upgrade to JUCE 5.4.3. Remove (probably) unused JUCE modules. Remove VST2 target (it's been end-of-life'd by Steinberg and by JUCE)
This commit is contained in:
		@ -1,169 +0,0 @@
 | 
			
		||||
$$CameraApi21
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class CameraDeviceStateCallback  extends CameraDevice.StateCallback
 | 
			
		||||
    {
 | 
			
		||||
        private native void cameraDeviceStateClosed       (long host, CameraDevice camera);
 | 
			
		||||
        private native void cameraDeviceStateDisconnected (long host, CameraDevice camera);
 | 
			
		||||
        private native void cameraDeviceStateError        (long host, CameraDevice camera, int error);
 | 
			
		||||
        private native void cameraDeviceStateOpened       (long host, CameraDevice camera);
 | 
			
		||||
 | 
			
		||||
        CameraDeviceStateCallback (long hostToUse)
 | 
			
		||||
        {
 | 
			
		||||
            host = hostToUse;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClosed (CameraDevice camera)
 | 
			
		||||
        {
 | 
			
		||||
            cameraDeviceStateClosed (host, camera);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onDisconnected (CameraDevice camera)
 | 
			
		||||
        {
 | 
			
		||||
            cameraDeviceStateDisconnected (host, camera);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onError (CameraDevice camera, int error)
 | 
			
		||||
        {
 | 
			
		||||
            cameraDeviceStateError (host, camera, error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onOpened (CameraDevice camera)
 | 
			
		||||
        {
 | 
			
		||||
            cameraDeviceStateOpened (host, camera);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private long host;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class CameraCaptureSessionStateCallback  extends CameraCaptureSession.StateCallback
 | 
			
		||||
    {
 | 
			
		||||
        private native void cameraCaptureSessionActive          (long host, CameraCaptureSession session);
 | 
			
		||||
        private native void cameraCaptureSessionClosed          (long host, CameraCaptureSession session);
 | 
			
		||||
        private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session);
 | 
			
		||||
        private native void cameraCaptureSessionConfigured      (long host, CameraCaptureSession session);
 | 
			
		||||
        private native void cameraCaptureSessionReady           (long host, CameraCaptureSession session);
 | 
			
		||||
 | 
			
		||||
        CameraCaptureSessionStateCallback (long hostToUse)
 | 
			
		||||
        {
 | 
			
		||||
            host = hostToUse;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onActive (CameraCaptureSession session)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionActive (host, session);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClosed (CameraCaptureSession session)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionClosed (host, session);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onConfigureFailed (CameraCaptureSession session)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionConfigureFailed (host, session);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onConfigured (CameraCaptureSession session)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionConfigured (host, session);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onReady (CameraCaptureSession session)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionReady (host, session);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private long host;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class CameraCaptureSessionCaptureCallback    extends CameraCaptureSession.CaptureCallback
 | 
			
		||||
    {
 | 
			
		||||
        private native void cameraCaptureSessionCaptureCompleted  (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result);
 | 
			
		||||
        private native void cameraCaptureSessionCaptureFailed     (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure);
 | 
			
		||||
        private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult);
 | 
			
		||||
        private native void cameraCaptureSessionCaptureStarted    (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber);
 | 
			
		||||
        private native void cameraCaptureSessionCaptureSequenceAborted   (long host, boolean isPreview, CameraCaptureSession session, int sequenceId);
 | 
			
		||||
        private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber);
 | 
			
		||||
 | 
			
		||||
        CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview)
 | 
			
		||||
        {
 | 
			
		||||
            host = hostToUse;
 | 
			
		||||
            preview = shouldBePreview;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request,
 | 
			
		||||
                                        TotalCaptureResult result)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionCaptureCompleted (host, preview, session, request, result);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionCaptureFailed (host, preview, session, request, failure);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request,
 | 
			
		||||
                                         CaptureResult partialResult)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp,
 | 
			
		||||
                                      long frameNumber)
 | 
			
		||||
        {
 | 
			
		||||
            cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private long host;
 | 
			
		||||
        private boolean preview;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class JuceOrientationEventListener    extends OrientationEventListener
 | 
			
		||||
    {
 | 
			
		||||
        private native void deviceOrientationChanged (long host, int orientation);
 | 
			
		||||
 | 
			
		||||
        public JuceOrientationEventListener (long hostToUse, Context context, int rate)
 | 
			
		||||
        {
 | 
			
		||||
            super (context, rate);
 | 
			
		||||
 | 
			
		||||
            host = hostToUse;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onOrientationChanged (int orientation)
 | 
			
		||||
        {
 | 
			
		||||
            deviceOrientationChanged (host, orientation);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private long host;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
CameraApi21$$
 | 
			
		||||
@ -1,999 +0,0 @@
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class BluetoothManager extends ScanCallback
 | 
			
		||||
    {
 | 
			
		||||
        BluetoothManager()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String[] getMidiBluetoothAddresses()
 | 
			
		||||
        {
 | 
			
		||||
            return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getHumanReadableStringForBluetoothAddress (String address)
 | 
			
		||||
        {
 | 
			
		||||
            BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
 | 
			
		||||
            return btDevice.getName();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int getBluetoothDeviceStatus (String address)
 | 
			
		||||
        {
 | 
			
		||||
            return getAndroidMidiDeviceManager().getBluetoothDeviceStatus (address);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void startStopScan (boolean shouldStart)
 | 
			
		||||
        {
 | 
			
		||||
            BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 | 
			
		||||
 | 
			
		||||
            if (bluetoothAdapter == null)
 | 
			
		||||
            {
 | 
			
		||||
                Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
 | 
			
		||||
 | 
			
		||||
            if (bluetoothLeScanner == null)
 | 
			
		||||
            {
 | 
			
		||||
                Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (shouldStart)
 | 
			
		||||
            {
 | 
			
		||||
                ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();
 | 
			
		||||
                scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));
 | 
			
		||||
 | 
			
		||||
                ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
 | 
			
		||||
                scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
 | 
			
		||||
                                   .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)
 | 
			
		||||
                                   .setScanMode (ScanSettings.MATCH_MODE_STICKY);
 | 
			
		||||
 | 
			
		||||
                bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),
 | 
			
		||||
                                              scanSettingsBuilder.build(),
 | 
			
		||||
                                              this);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                bluetoothLeScanner.stopScan (this);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public boolean pairBluetoothMidiDevice(String address)
 | 
			
		||||
        {
 | 
			
		||||
            BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
 | 
			
		||||
 | 
			
		||||
            if (btDevice == null)
 | 
			
		||||
            {
 | 
			
		||||
                Log.d ("JUCE", "failed to create buletooth device from address");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void unpairBluetoothMidiDevice (String address)
 | 
			
		||||
        {
 | 
			
		||||
            getAndroidMidiDeviceManager().unpairBluetoothDevice (address);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onScanFailed (int errorCode)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onScanResult (int callbackType, ScanResult result)
 | 
			
		||||
        {
 | 
			
		||||
            if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
 | 
			
		||||
                 || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
 | 
			
		||||
            {
 | 
			
		||||
                BluetoothDevice device = result.getDevice();
 | 
			
		||||
 | 
			
		||||
                if (device != null)
 | 
			
		||||
                    bluetoothMidiDevices.add (device.getAddress());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)
 | 
			
		||||
            {
 | 
			
		||||
                Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");
 | 
			
		||||
                BluetoothDevice device = result.getDevice();
 | 
			
		||||
 | 
			
		||||
                if (device != null)
 | 
			
		||||
                {
 | 
			
		||||
                    bluetoothMidiDevices.remove (device.getAddress());
 | 
			
		||||
                    unpairBluetoothMidiDevice (device.getAddress());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onBatchScanResults (List<ScanResult> results)
 | 
			
		||||
        {
 | 
			
		||||
            for (ScanResult result : results)
 | 
			
		||||
                onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private BluetoothLeScanner scanner;
 | 
			
		||||
        private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
 | 
			
		||||
 | 
			
		||||
        private HashSet<String> bluetoothMidiDevices = new HashSet<String>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort
 | 
			
		||||
    {
 | 
			
		||||
        private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);
 | 
			
		||||
 | 
			
		||||
        public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse)
 | 
			
		||||
        {
 | 
			
		||||
            owner = mm;
 | 
			
		||||
            androidPort = actualPort;
 | 
			
		||||
            portPath = portPathToUse;
 | 
			
		||||
            juceHost = hostToUse;
 | 
			
		||||
            isConnected = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void finalize() throws Throwable
 | 
			
		||||
        {
 | 
			
		||||
            close();
 | 
			
		||||
            super.finalize();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean isInputPort()
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void start()
 | 
			
		||||
        {
 | 
			
		||||
            if (owner != null && androidPort != null && ! isConnected) {
 | 
			
		||||
                androidPort.connect(this);
 | 
			
		||||
                isConnected = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void stop()
 | 
			
		||||
        {
 | 
			
		||||
            if (owner != null && androidPort != null && isConnected) {
 | 
			
		||||
                androidPort.disconnect(this);
 | 
			
		||||
                isConnected = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void close()
 | 
			
		||||
        {
 | 
			
		||||
            if (androidPort != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    androidPort.close();
 | 
			
		||||
                } catch (IOException exception) {
 | 
			
		||||
                    Log.d("JUCE", "IO Exception while closing port");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (owner != null)
 | 
			
		||||
                owner.removePort (portPath);
 | 
			
		||||
 | 
			
		||||
            owner = null;
 | 
			
		||||
            androidPort = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onSend (byte[] msg, int offset, int count, long timestamp)
 | 
			
		||||
        {
 | 
			
		||||
            if (count > 0)
 | 
			
		||||
                handleReceive (juceHost, msg, offset, count, timestamp);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onFlush()
 | 
			
		||||
        {}
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void sendMidi (byte[] msg, int offset, int count)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        MidiDeviceManager owner;
 | 
			
		||||
        MidiOutputPort androidPort;
 | 
			
		||||
        MidiPortPath portPath;
 | 
			
		||||
        long juceHost;
 | 
			
		||||
        boolean isConnected;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class JuceMidiOutputPort implements JuceMidiPort
 | 
			
		||||
    {
 | 
			
		||||
        public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse)
 | 
			
		||||
        {
 | 
			
		||||
            owner = mm;
 | 
			
		||||
            androidPort = actualPort;
 | 
			
		||||
            portPath = portPathToUse;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void finalize() throws Throwable
 | 
			
		||||
        {
 | 
			
		||||
            close();
 | 
			
		||||
            super.finalize();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean isInputPort()
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void start()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void stop()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void sendMidi (byte[] msg, int offset, int count)
 | 
			
		||||
        {
 | 
			
		||||
            if (androidPort != null)
 | 
			
		||||
            {
 | 
			
		||||
                try {
 | 
			
		||||
                    androidPort.send(msg, offset, count);
 | 
			
		||||
                } catch (IOException exception)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.d ("JUCE", "send midi had IO exception");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void close()
 | 
			
		||||
        {
 | 
			
		||||
            if (androidPort != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    androidPort.close();
 | 
			
		||||
                } catch (IOException exception) {
 | 
			
		||||
                    Log.d("JUCE", "IO Exception while closing port");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (owner != null)
 | 
			
		||||
                owner.removePort (portPath);
 | 
			
		||||
 | 
			
		||||
            owner = null;
 | 
			
		||||
            androidPort = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        MidiDeviceManager owner;
 | 
			
		||||
        MidiInputPort androidPort;
 | 
			
		||||
        MidiPortPath portPath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class MidiPortPath extends Object
 | 
			
		||||
    {
 | 
			
		||||
        public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex)
 | 
			
		||||
        {
 | 
			
		||||
            deviceId = deviceIdToUse;
 | 
			
		||||
            isInput = direction;
 | 
			
		||||
            portIndex = androidIndex;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int deviceId;
 | 
			
		||||
        public int portIndex;
 | 
			
		||||
        public boolean isInput;
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int hashCode()
 | 
			
		||||
        {
 | 
			
		||||
            Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127));
 | 
			
		||||
            return i.hashCode() * (isInput ? -1 : 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean equals (Object obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj == null)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (getClass() != obj.getClass())
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            MidiPortPath other = (MidiPortPath) obj;
 | 
			
		||||
            return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener
 | 
			
		||||
    {
 | 
			
		||||
        //==============================================================================
 | 
			
		||||
        private class DummyBluetoothGattCallback extends BluetoothGattCallback
 | 
			
		||||
        {
 | 
			
		||||
            public DummyBluetoothGattCallback (MidiDeviceManager mm)
 | 
			
		||||
            {
 | 
			
		||||
                super();
 | 
			
		||||
                owner = mm;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
 | 
			
		||||
            {
 | 
			
		||||
                if (newState == BluetoothProfile.STATE_CONNECTED)
 | 
			
		||||
                {
 | 
			
		||||
                    gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
 | 
			
		||||
                    owner.pairBluetoothDeviceStepTwo (gatt.getDevice());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
 | 
			
		||||
            public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
 | 
			
		||||
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
 | 
			
		||||
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}
 | 
			
		||||
            public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
 | 
			
		||||
            public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
 | 
			
		||||
            public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {}
 | 
			
		||||
            public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {}
 | 
			
		||||
            public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}
 | 
			
		||||
 | 
			
		||||
            private MidiDeviceManager owner;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //==============================================================================
 | 
			
		||||
        private class MidiDeviceOpenTask extends java.util.TimerTask
 | 
			
		||||
        {
 | 
			
		||||
            public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse)
 | 
			
		||||
            {
 | 
			
		||||
                owner = deviceManager;
 | 
			
		||||
                midiDevice = device;
 | 
			
		||||
                btGatt = gattToUse;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean cancel()
 | 
			
		||||
            {
 | 
			
		||||
                synchronized (MidiDeviceOpenTask.class)
 | 
			
		||||
                {
 | 
			
		||||
                    owner = null;
 | 
			
		||||
                    boolean retval = super.cancel();
 | 
			
		||||
 | 
			
		||||
                    if (btGatt != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        btGatt.disconnect();
 | 
			
		||||
                        btGatt.close();
 | 
			
		||||
 | 
			
		||||
                        btGatt = null;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (midiDevice != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        try
 | 
			
		||||
                        {
 | 
			
		||||
                            midiDevice.close();
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (IOException e)
 | 
			
		||||
                        {}
 | 
			
		||||
 | 
			
		||||
                        midiDevice = null;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return retval;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public String getBluetoothAddress()
 | 
			
		||||
            {
 | 
			
		||||
                synchronized (MidiDeviceOpenTask.class)
 | 
			
		||||
                {
 | 
			
		||||
                    if (midiDevice != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        MidiDeviceInfo info = midiDevice.getInfo();
 | 
			
		||||
                        if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
 | 
			
		||||
                        {
 | 
			
		||||
                            BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
 | 
			
		||||
                            if (btDevice != null)
 | 
			
		||||
                                return btDevice.getAddress();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return "";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public BluetoothGatt getGatt() { return btGatt; }
 | 
			
		||||
 | 
			
		||||
            public int getID()
 | 
			
		||||
            {
 | 
			
		||||
                return midiDevice.getInfo().getId();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run()
 | 
			
		||||
            {
 | 
			
		||||
                synchronized (MidiDeviceOpenTask.class)
 | 
			
		||||
                {
 | 
			
		||||
                    if (owner != null && midiDevice != null)
 | 
			
		||||
                        owner.onDeviceOpenedDelayed (midiDevice);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            private MidiDeviceManager owner;
 | 
			
		||||
            private MidiDevice midiDevice;
 | 
			
		||||
            private BluetoothGatt btGatt;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //==============================================================================
 | 
			
		||||
        public MidiDeviceManager()
 | 
			
		||||
        {
 | 
			
		||||
            manager = (MidiManager) getSystemService (MIDI_SERVICE);
 | 
			
		||||
 | 
			
		||||
            if (manager == null)
 | 
			
		||||
            {
 | 
			
		||||
                Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            openPorts = new HashMap<MidiPortPath, WeakReference<JuceMidiPort>> ();
 | 
			
		||||
            midiDevices = new ArrayList<Pair<MidiDevice,BluetoothGatt>>();
 | 
			
		||||
            openTasks = new HashMap<Integer, MidiDeviceOpenTask>();
 | 
			
		||||
            btDevicesPairing = new HashMap<String, BluetoothGatt>();
 | 
			
		||||
 | 
			
		||||
            MidiDeviceInfo[] foundDevices = manager.getDevices();
 | 
			
		||||
            for (MidiDeviceInfo info : foundDevices)
 | 
			
		||||
                onDeviceAdded (info);
 | 
			
		||||
 | 
			
		||||
            manager.registerDeviceCallback (this, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected void finalize() throws Throwable
 | 
			
		||||
        {
 | 
			
		||||
            manager.unregisterDeviceCallback (this);
 | 
			
		||||
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                btDevicesPairing.clear();
 | 
			
		||||
 | 
			
		||||
                for (Integer deviceID : openTasks.keySet())
 | 
			
		||||
                    openTasks.get (deviceID).cancel();
 | 
			
		||||
 | 
			
		||||
                openTasks = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (MidiPortPath key : openPorts.keySet())
 | 
			
		||||
                openPorts.get (key).get().close();
 | 
			
		||||
 | 
			
		||||
            openPorts = null;
 | 
			
		||||
 | 
			
		||||
            for (Pair<MidiDevice, BluetoothGatt> device : midiDevices)
 | 
			
		||||
            {
 | 
			
		||||
                if (device.second != null)
 | 
			
		||||
                {
 | 
			
		||||
                    device.second.disconnect();
 | 
			
		||||
                    device.second.close();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                device.first.close();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            midiDevices.clear();
 | 
			
		||||
 | 
			
		||||
            super.finalize();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String[] getJuceAndroidMidiInputDevices()
 | 
			
		||||
        {
 | 
			
		||||
            return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String[] getJuceAndroidMidiOutputDevices()
 | 
			
		||||
        {
 | 
			
		||||
            return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private String[] getJuceAndroidMidiDevices (int portType)
 | 
			
		||||
        {
 | 
			
		||||
            // only update the list when JUCE asks for a new list
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                deviceInfos = getDeviceInfos();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ArrayList<String> portNames = new ArrayList<String>();
 | 
			
		||||
 | 
			
		||||
            int index = 0;
 | 
			
		||||
            for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index))
 | 
			
		||||
                portNames.add (getPortName (portInfo));
 | 
			
		||||
 | 
			
		||||
            String[] names = new String[portNames.size()];
 | 
			
		||||
            return portNames.toArray (names);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT);
 | 
			
		||||
                MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index);
 | 
			
		||||
 | 
			
		||||
                if (portInfo != null)
 | 
			
		||||
                {
 | 
			
		||||
                    // ports must be opened exclusively!
 | 
			
		||||
                    if (openPorts.containsKey (portInfo))
 | 
			
		||||
                        return null;
 | 
			
		||||
 | 
			
		||||
                    Pair<MidiDevice,BluetoothGatt> devicePair = getMidiDevicePairForId (portInfo.deviceId);
 | 
			
		||||
 | 
			
		||||
                    if (devicePair != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        MidiDevice device = devicePair.first;
 | 
			
		||||
                        if (device != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            JuceMidiPort juceMidiPort = null;
 | 
			
		||||
 | 
			
		||||
                            if (isInput)
 | 
			
		||||
                            {
 | 
			
		||||
                                MidiOutputPort outputPort = device.openOutputPort(portInfo.portIndex);
 | 
			
		||||
 | 
			
		||||
                                if (outputPort != null)
 | 
			
		||||
                                    juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host);
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                MidiInputPort inputPort = device.openInputPort(portInfo.portIndex);
 | 
			
		||||
 | 
			
		||||
                                if (inputPort != null)
 | 
			
		||||
                                    juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if (juceMidiPort != null)
 | 
			
		||||
                            {
 | 
			
		||||
                                openPorts.put(portInfo, new WeakReference<JuceMidiPort>(juceMidiPort));
 | 
			
		||||
 | 
			
		||||
                                return juceMidiPort;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
 | 
			
		||||
        {
 | 
			
		||||
            return openMidiPortWithJuceIndex (index, host, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
 | 
			
		||||
        {
 | 
			
		||||
            return openMidiPortWithJuceIndex (index, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* 0: unpaired, 1: paired, 2: pairing */
 | 
			
		||||
        public int getBluetoothDeviceStatus (String address)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                if (! address.isEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    if (findMidiDeviceForBluetoothAddress (address) != null)
 | 
			
		||||
                        return 1;
 | 
			
		||||
 | 
			
		||||
                    if (btDevicesPairing.containsKey (address))
 | 
			
		||||
                        return 2;
 | 
			
		||||
 | 
			
		||||
                    if (findOpenTaskForBluetoothAddress (address) != null)
 | 
			
		||||
                        return 2;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public boolean pairBluetoothDevice (BluetoothDevice btDevice)
 | 
			
		||||
        {
 | 
			
		||||
            String btAddress = btDevice.getAddress();
 | 
			
		||||
            if (btAddress.isEmpty())
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                if (getBluetoothDeviceStatus (btAddress) != 0)
 | 
			
		||||
                    return false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                btDevicesPairing.put (btDevice.getAddress(), null);
 | 
			
		||||
                BluetoothGatt gatt = btDevice.connectGatt (getApplicationContext(), true, new DummyBluetoothGattCallback (this));
 | 
			
		||||
 | 
			
		||||
                if (gatt != null)
 | 
			
		||||
                {
 | 
			
		||||
                    btDevicesPairing.put (btDevice.getAddress(), gatt);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    pairBluetoothDeviceStepTwo (btDevice);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice)
 | 
			
		||||
        {
 | 
			
		||||
            manager.openBluetoothDevice(btDevice, this, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void unpairBluetoothDevice (String address)
 | 
			
		||||
        {
 | 
			
		||||
            if (address.isEmpty())
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                if (btDevicesPairing.containsKey (address))
 | 
			
		||||
                {
 | 
			
		||||
                    BluetoothGatt gatt = btDevicesPairing.get (address);
 | 
			
		||||
                    if (gatt != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        gatt.disconnect();
 | 
			
		||||
                        gatt.close();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    btDevicesPairing.remove (address);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address);
 | 
			
		||||
                if (openTask != null)
 | 
			
		||||
                {
 | 
			
		||||
                    int deviceID = openTask.getID();
 | 
			
		||||
                    openTask.cancel();
 | 
			
		||||
                    openTasks.remove (deviceID);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (address);
 | 
			
		||||
                if (midiDevicePair != null)
 | 
			
		||||
                {
 | 
			
		||||
                    MidiDevice midiDevice = midiDevicePair.first;
 | 
			
		||||
                    onDeviceRemoved (midiDevice.getInfo());
 | 
			
		||||
 | 
			
		||||
                    try {
 | 
			
		||||
                        midiDevice.close();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (IOException exception)
 | 
			
		||||
                    {
 | 
			
		||||
                        Log.d ("JUCE", "IOException while closing midi device");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Pair<MidiDevice, BluetoothGatt> findMidiDeviceForBluetoothAddress (String address)
 | 
			
		||||
        {
 | 
			
		||||
            for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
 | 
			
		||||
            {
 | 
			
		||||
                MidiDeviceInfo info = midiDevice.first.getInfo();
 | 
			
		||||
                if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
 | 
			
		||||
                {
 | 
			
		||||
                    BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
 | 
			
		||||
                    if (btDevice != null && btDevice.getAddress().equals (address))
 | 
			
		||||
                        return midiDevice;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address)
 | 
			
		||||
        {
 | 
			
		||||
            for (Integer deviceID : openTasks.keySet())
 | 
			
		||||
            {
 | 
			
		||||
                MidiDeviceOpenTask openTask = openTasks.get (deviceID);
 | 
			
		||||
                if (openTask.getBluetoothAddress().equals (address))
 | 
			
		||||
                    return openTask;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void removePort (MidiPortPath path)
 | 
			
		||||
        {
 | 
			
		||||
            openPorts.remove (path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getInputPortNameForJuceIndex (int index)
 | 
			
		||||
        {
 | 
			
		||||
            MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index);
 | 
			
		||||
            if (portInfo != null)
 | 
			
		||||
                return getPortName (portInfo);
 | 
			
		||||
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getOutputPortNameForJuceIndex (int index)
 | 
			
		||||
        {
 | 
			
		||||
            MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index);
 | 
			
		||||
            if (portInfo != null)
 | 
			
		||||
                return getPortName (portInfo);
 | 
			
		||||
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onDeviceAdded (MidiDeviceInfo info)
 | 
			
		||||
        {
 | 
			
		||||
            // only add standard midi devices
 | 
			
		||||
            if (info.getType() == info.TYPE_BLUETOOTH)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            manager.openDevice (info, this, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onDeviceRemoved (MidiDeviceInfo info)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                Pair<MidiDevice, BluetoothGatt> devicePair = getMidiDevicePairForId (info.getId());
 | 
			
		||||
 | 
			
		||||
                if (devicePair != null)
 | 
			
		||||
                {
 | 
			
		||||
                    MidiDevice midiDevice = devicePair.first;
 | 
			
		||||
                    BluetoothGatt gatt = devicePair.second;
 | 
			
		||||
 | 
			
		||||
                    // close all ports that use this device
 | 
			
		||||
                    boolean removedPort = true;
 | 
			
		||||
 | 
			
		||||
                    while (removedPort == true)
 | 
			
		||||
                    {
 | 
			
		||||
                        removedPort = false;
 | 
			
		||||
                        for (MidiPortPath key : openPorts.keySet())
 | 
			
		||||
                        {
 | 
			
		||||
                            if (key.deviceId == info.getId())
 | 
			
		||||
                            {
 | 
			
		||||
                                openPorts.get(key).get().close();
 | 
			
		||||
                                removedPort = true;
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (gatt != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        gatt.disconnect();
 | 
			
		||||
                        gatt.close();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    midiDevices.remove (devicePair);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onDeviceStatusChanged (MidiDeviceStatus status)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onDeviceOpened (MidiDevice theDevice)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                MidiDeviceInfo info = theDevice.getInfo();
 | 
			
		||||
                int deviceID = info.getId();
 | 
			
		||||
                BluetoothGatt gatt = null;
 | 
			
		||||
                boolean isBluetooth = false;
 | 
			
		||||
 | 
			
		||||
                if (! openTasks.containsKey (deviceID))
 | 
			
		||||
                {
 | 
			
		||||
                    if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
 | 
			
		||||
                    {
 | 
			
		||||
                        isBluetooth = true;
 | 
			
		||||
                        BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
 | 
			
		||||
                        if (btDevice != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            String btAddress = btDevice.getAddress();
 | 
			
		||||
                            if (btDevicesPairing.containsKey (btAddress))
 | 
			
		||||
                            {
 | 
			
		||||
                                gatt = btDevicesPairing.get (btAddress);
 | 
			
		||||
                                btDevicesPairing.remove (btAddress);
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                // unpair was called in the mean time
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
 | 
			
		||||
                                    if (midiDevicePair != null)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        gatt = midiDevicePair.second;
 | 
			
		||||
 | 
			
		||||
                                        if (gatt != null)
 | 
			
		||||
                                        {
 | 
			
		||||
                                            gatt.disconnect();
 | 
			
		||||
                                            gatt.close();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
 | 
			
		||||
                                    theDevice.close();
 | 
			
		||||
                                }
 | 
			
		||||
                                catch (IOException e)
 | 
			
		||||
                                {}
 | 
			
		||||
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt);
 | 
			
		||||
                    openTasks.put (deviceID, openTask);
 | 
			
		||||
 | 
			
		||||
                    new java.util.Timer().schedule (openTask, (isBluetooth ? 2000 : 100));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onDeviceOpenedDelayed (MidiDevice theDevice)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                int deviceID = theDevice.getInfo().getId();
 | 
			
		||||
 | 
			
		||||
                if (openTasks.containsKey (deviceID))
 | 
			
		||||
                {
 | 
			
		||||
                    if (! midiDevices.contains(theDevice))
 | 
			
		||||
                    {
 | 
			
		||||
                        BluetoothGatt gatt = openTasks.get (deviceID).getGatt();
 | 
			
		||||
                        openTasks.remove (deviceID);
 | 
			
		||||
                        midiDevices.add (new Pair<MidiDevice,BluetoothGatt> (theDevice, gatt));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // unpair was called in the mean time
 | 
			
		||||
                    MidiDeviceInfo info = theDevice.getInfo();
 | 
			
		||||
                    BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
 | 
			
		||||
                    if (btDevice != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        String btAddress = btDevice.getAddress();
 | 
			
		||||
                        Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
 | 
			
		||||
                        if (midiDevicePair != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            BluetoothGatt gatt = midiDevicePair.second;
 | 
			
		||||
 | 
			
		||||
                            if (gatt != null)
 | 
			
		||||
                            {
 | 
			
		||||
                                gatt.disconnect();
 | 
			
		||||
                                gatt.close();
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        theDevice.close();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (IOException e)
 | 
			
		||||
                    {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getPortName(MidiPortPath path)
 | 
			
		||||
        {
 | 
			
		||||
            int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
 | 
			
		||||
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                for (MidiDeviceInfo info : deviceInfos)
 | 
			
		||||
                {
 | 
			
		||||
                    int localIndex = 0;
 | 
			
		||||
                    if (info.getId() == path.deviceId)
 | 
			
		||||
                    {
 | 
			
		||||
                        for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
 | 
			
		||||
                        {
 | 
			
		||||
                            int portType = portInfo.getType();
 | 
			
		||||
                            if (portType == portTypeToFind)
 | 
			
		||||
                            {
 | 
			
		||||
                                int portIndex = portInfo.getPortNumber();
 | 
			
		||||
                                if (portIndex == path.portIndex)
 | 
			
		||||
                                {
 | 
			
		||||
                                    String portName = portInfo.getName();
 | 
			
		||||
                                    if (portName.isEmpty())
 | 
			
		||||
                                        portName = (String) info.getProperties().get(info.PROPERTY_NAME);
 | 
			
		||||
 | 
			
		||||
                                    return portName;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)
 | 
			
		||||
        {
 | 
			
		||||
            int portIdx = 0;
 | 
			
		||||
            for (MidiDeviceInfo info : deviceInfos)
 | 
			
		||||
            {
 | 
			
		||||
                for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
 | 
			
		||||
                {
 | 
			
		||||
                    if (portInfo.getType() == portType)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (portIdx == juceIndex)
 | 
			
		||||
                            return new MidiPortPath (info.getId(),
 | 
			
		||||
                                    (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
 | 
			
		||||
                                    portInfo.getPortNumber());
 | 
			
		||||
 | 
			
		||||
                        portIdx++;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private MidiDeviceInfo[] getDeviceInfos()
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()];
 | 
			
		||||
 | 
			
		||||
                int idx = 0;
 | 
			
		||||
                for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
 | 
			
		||||
                    infos[idx++] = midiDevice.first.getInfo();
 | 
			
		||||
 | 
			
		||||
                return infos;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Pair<MidiDevice, BluetoothGatt> getMidiDevicePairForId (int deviceId)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (MidiDeviceManager.class)
 | 
			
		||||
            {
 | 
			
		||||
                for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
 | 
			
		||||
                    if (midiDevice.first.getInfo().getId() == deviceId)
 | 
			
		||||
                        return midiDevice;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private MidiManager manager;
 | 
			
		||||
        private HashMap<String, BluetoothGatt> btDevicesPairing;
 | 
			
		||||
        private HashMap<Integer, MidiDeviceOpenTask> openTasks;
 | 
			
		||||
        private ArrayList<Pair<MidiDevice, BluetoothGatt>> midiDevices;
 | 
			
		||||
        private MidiDeviceInfo[] deviceInfos;
 | 
			
		||||
        private HashMap<MidiPortPath, WeakReference<JuceMidiPort>> openPorts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MidiDeviceManager getAndroidMidiDeviceManager()
 | 
			
		||||
    {
 | 
			
		||||
        if (getSystemService (MIDI_SERVICE) == null)
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        synchronized (JuceAppActivity.class)
 | 
			
		||||
        {
 | 
			
		||||
            if (midiDeviceManager == null)
 | 
			
		||||
                midiDeviceManager = new MidiDeviceManager();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return midiDeviceManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BluetoothManager getAndroidBluetoothManager()
 | 
			
		||||
    {
 | 
			
		||||
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
 | 
			
		||||
 | 
			
		||||
        if (adapter == null)
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        if (adapter.getBluetoothLeScanner() == null)
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        synchronized (JuceAppActivity.class)
 | 
			
		||||
        {
 | 
			
		||||
            if (bluetoothManager == null)
 | 
			
		||||
                bluetoothManager = new BluetoothManager();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return bluetoothManager;
 | 
			
		||||
    }
 | 
			
		||||
@ -1,85 +0,0 @@
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class BluetoothManager
 | 
			
		||||
    {
 | 
			
		||||
        BluetoothManager()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String[] getMidiBluetoothAddresses()
 | 
			
		||||
        {
 | 
			
		||||
            String[] bluetoothAddresses = new String[0];
 | 
			
		||||
            return bluetoothAddresses;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getHumanReadableStringForBluetoothAddress (String address)
 | 
			
		||||
        {
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int getBluetoothDeviceStatus (String address)
 | 
			
		||||
        {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void startStopScan (boolean shouldStart)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public boolean pairBluetoothMidiDevice(String address)
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void unpairBluetoothMidiDevice (String address)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    public class MidiDeviceManager
 | 
			
		||||
    {
 | 
			
		||||
        public MidiDeviceManager()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String[] getJuceAndroidMidiInputDevices()
 | 
			
		||||
        {
 | 
			
		||||
            return new String[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String[] getJuceAndroidMidiOutputDevices()
 | 
			
		||||
        {
 | 
			
		||||
            return new String[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getInputPortNameForJuceIndex (int index)
 | 
			
		||||
        {
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String getOutputPortNameForJuceIndex (int index)
 | 
			
		||||
        {
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public MidiDeviceManager getAndroidMidiDeviceManager()
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BluetoothManager getAndroidBluetoothManager()
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)
 | 
			
		||||
    {
 | 
			
		||||
        boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);
 | 
			
		||||
 | 
			
		||||
        if (! permissionsGranted)
 | 
			
		||||
            Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID));
 | 
			
		||||
 | 
			
		||||
        Long ptrToCallback = permissionCallbackPtrMap.get (permissionID);
 | 
			
		||||
        permissionCallbackPtrMap.remove (permissionID);
 | 
			
		||||
        androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback);
 | 
			
		||||
    }
 | 
			
		||||
@ -1,138 +0,0 @@
 | 
			
		||||
package com.juce;
 | 
			
		||||
 | 
			
		||||
import android.content.ContentProvider;
 | 
			
		||||
import android.content.ContentValues;
 | 
			
		||||
import android.content.res.AssetFileDescriptor;
 | 
			
		||||
import android.content.res.Resources;
 | 
			
		||||
import android.database.Cursor;
 | 
			
		||||
import android.database.MatrixCursor;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.FileObserver;
 | 
			
		||||
import android.os.ParcelFileDescriptor;
 | 
			
		||||
import java.lang.String;
 | 
			
		||||
 | 
			
		||||
public final class SharingContentProvider extends ContentProvider
 | 
			
		||||
{
 | 
			
		||||
    private Object lock = new Object();
 | 
			
		||||
 | 
			
		||||
    private native void contentSharerFileObserverEvent (long host, int event, String path);
 | 
			
		||||
 | 
			
		||||
    private native Cursor contentSharerQuery (Uri uri, String[] projection, String selection,
 | 
			
		||||
                                              String[] selectionArgs, String sortOrder);
 | 
			
		||||
 | 
			
		||||
    private native void contentSharerCursorClosed (long host);
 | 
			
		||||
 | 
			
		||||
    private native AssetFileDescriptor contentSharerOpenFile (Uri uri, String mode);
 | 
			
		||||
    private native String[] contentSharerGetStreamTypes (Uri uri, String mimeTypeFilter);
 | 
			
		||||
 | 
			
		||||
    public final class ProviderFileObserver extends FileObserver
 | 
			
		||||
    {
 | 
			
		||||
        public ProviderFileObserver (long hostToUse, String path, int mask)
 | 
			
		||||
        {
 | 
			
		||||
            super (path, mask);
 | 
			
		||||
 | 
			
		||||
            host = hostToUse;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void onEvent (int event, String path)
 | 
			
		||||
        {
 | 
			
		||||
            contentSharerFileObserverEvent (host, event, path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private long host;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final class ProviderCursor extends MatrixCursor
 | 
			
		||||
    {
 | 
			
		||||
        ProviderCursor (long hostToUse, String[] columnNames)
 | 
			
		||||
        {
 | 
			
		||||
            super (columnNames);
 | 
			
		||||
 | 
			
		||||
            host = hostToUse;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void close()
 | 
			
		||||
        {
 | 
			
		||||
            super.close();
 | 
			
		||||
 | 
			
		||||
            contentSharerCursorClosed (host);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private long host;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean onCreate()
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Cursor query (Uri url, String[] projection, String selection,
 | 
			
		||||
                         String[] selectionArgs, String sortOrder)
 | 
			
		||||
    {
 | 
			
		||||
        synchronized (lock)
 | 
			
		||||
        {
 | 
			
		||||
            return contentSharerQuery (url, projection, selection, selectionArgs, sortOrder);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Uri insert (Uri uri, ContentValues values)
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int update (Uri uri, ContentValues values, String selection,
 | 
			
		||||
                       String[] selectionArgs)
 | 
			
		||||
    {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int delete (Uri uri, String selection, String[] selectionArgs)
 | 
			
		||||
    {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getType (Uri uri)
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AssetFileDescriptor openAssetFile (Uri uri, String mode)
 | 
			
		||||
    {
 | 
			
		||||
        synchronized (lock)
 | 
			
		||||
        {
 | 
			
		||||
            return contentSharerOpenFile (uri, mode);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ParcelFileDescriptor openFile (Uri uri, String mode)
 | 
			
		||||
    {
 | 
			
		||||
        synchronized (lock)
 | 
			
		||||
        {
 | 
			
		||||
            AssetFileDescriptor result = contentSharerOpenFile (uri, mode);
 | 
			
		||||
 | 
			
		||||
            if (result != null)
 | 
			
		||||
                return result.getParcelFileDescriptor();
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
$$ContentProviderApi11
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getStreamTypes (Uri uri, String mimeTypeFilter)
 | 
			
		||||
    {
 | 
			
		||||
        synchronized (lock)
 | 
			
		||||
        {
 | 
			
		||||
            return contentSharerGetStreamTypes (uri, mimeTypeFilter);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
ContentProviderApi11$$
 | 
			
		||||
}
 | 
			
		||||
@ -1,69 +0,0 @@
 | 
			
		||||
$$WebViewNativeApi23    private native void webViewReceivedError (long host, WebView view, WebResourceRequest request, WebResourceError error);WebViewNativeApi23$$
 | 
			
		||||
$$WebViewNativeApi21    private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse);WebViewNativeApi21$$
 | 
			
		||||
 | 
			
		||||
$$WebViewApi1_10
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onPageStarted (WebView view, String url, Bitmap favicon)
 | 
			
		||||
        {
 | 
			
		||||
            if (host != 0)
 | 
			
		||||
                webViewPageLoadStarted (host, view, url);
 | 
			
		||||
        }
 | 
			
		||||
WebViewApi1_10$$
 | 
			
		||||
 | 
			
		||||
$$WebViewApi11_20
 | 
			
		||||
        @Override
 | 
			
		||||
        public WebResourceResponse shouldInterceptRequest (WebView view, String url)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (hostLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (host != 0)
 | 
			
		||||
                {
 | 
			
		||||
                    boolean shouldLoad = webViewPageLoadStarted (host, view, url);
 | 
			
		||||
 | 
			
		||||
                    if (shouldLoad)
 | 
			
		||||
                        return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new WebResourceResponse ("text/html", null, null);
 | 
			
		||||
        }
 | 
			
		||||
WebViewApi11_20$$
 | 
			
		||||
 | 
			
		||||
$$WebViewApi21
 | 
			
		||||
        @Override
 | 
			
		||||
        public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (hostLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (host != 0)
 | 
			
		||||
                {
 | 
			
		||||
                    boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString());
 | 
			
		||||
 | 
			
		||||
                    if (shouldLoad)
 | 
			
		||||
                        return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new WebResourceResponse ("text/html", null, null);
 | 
			
		||||
        }
 | 
			
		||||
WebViewApi21$$
 | 
			
		||||
 | 
			
		||||
$$WebViewApi23
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error)
 | 
			
		||||
        {
 | 
			
		||||
            if (host == 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            webViewReceivedError (host, view, request, error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
 | 
			
		||||
        {
 | 
			
		||||
            if (host == 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            webViewReceivedHttpError (host, view, request, errorResponse);
 | 
			
		||||
        }
 | 
			
		||||
WebViewApi23$$
 | 
			
		||||
@ -1,971 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
  ==============================================================================
 | 
			
		||||
 | 
			
		||||
   This file is part of the JUCE library.
 | 
			
		||||
   Copyright (c) 2017 - ROLI Ltd.
 | 
			
		||||
 | 
			
		||||
   JUCE is an open source library subject to commercial or open-source
 | 
			
		||||
   licensing.
 | 
			
		||||
 | 
			
		||||
   The code included in this file is provided under the terms of the ISC license
 | 
			
		||||
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
 | 
			
		||||
   To use, copy, modify, and/or distribute this software for any purpose with or
 | 
			
		||||
   without fee is hereby granted provided that the above copyright notice and
 | 
			
		||||
   this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 | 
			
		||||
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 | 
			
		||||
   DISCLAIMED.
 | 
			
		||||
 | 
			
		||||
  ==============================================================================
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package com.android.vending.billing;
 | 
			
		||||
/**
 | 
			
		||||
 * InAppBillingService is the service that provides in-app billing version 3 and beyond.
 | 
			
		||||
 * This service provides the following features:
 | 
			
		||||
 * 1. Provides a new API to get details of in-app items published for the app including
 | 
			
		||||
 *    price, type, title and description.
 | 
			
		||||
 * 2. The purchase flow is synchronous and purchase information is available immediately
 | 
			
		||||
 *    after it completes.
 | 
			
		||||
 * 3. Purchase information of in-app purchases is maintained within the Google Play system
 | 
			
		||||
 *    till the purchase is consumed.
 | 
			
		||||
 * 4. An API to consume a purchase of an inapp item. All purchases of one-time
 | 
			
		||||
 *    in-app items are consumable and thereafter can be purchased again.
 | 
			
		||||
 * 5. An API to get current purchases of the user immediately. This will not contain any
 | 
			
		||||
 *    consumed purchases.
 | 
			
		||||
 *
 | 
			
		||||
 * All calls will give a response code with the following possible values
 | 
			
		||||
 * RESULT_OK = 0 - success
 | 
			
		||||
 * RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog
 | 
			
		||||
 * RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down
 | 
			
		||||
 * RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested
 | 
			
		||||
 * RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase
 | 
			
		||||
 * RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API
 | 
			
		||||
 * RESULT_ERROR = 6 - Fatal error during the API action
 | 
			
		||||
 * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
 | 
			
		||||
 * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
 | 
			
		||||
 */
 | 
			
		||||
public interface IInAppBillingService extends android.os.IInterface
 | 
			
		||||
    {
 | 
			
		||||
        /** Local-side IPC implementation stub class. */
 | 
			
		||||
        public static abstract class Stub extends android.os.Binder implements com.android.vending.billing.IInAppBillingService
 | 
			
		||||
        {
 | 
			
		||||
            private static final java.lang.String DESCRIPTOR = "com.android.vending.billing.IInAppBillingService";
 | 
			
		||||
            /** Construct the stub at attach it to the interface. */
 | 
			
		||||
            public Stub()
 | 
			
		||||
            {
 | 
			
		||||
                this.attachInterface(this, DESCRIPTOR);
 | 
			
		||||
            }
 | 
			
		||||
            /**
 | 
			
		||||
             * Cast an IBinder object into an com.android.vending.billing.IInAppBillingService interface,
 | 
			
		||||
             * generating a proxy if needed.
 | 
			
		||||
             */
 | 
			
		||||
            public static com.android.vending.billing.IInAppBillingService asInterface(android.os.IBinder obj)
 | 
			
		||||
            {
 | 
			
		||||
                if ((obj==null)) {
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
 | 
			
		||||
                if (((iin!=null)&&(iin instanceof com.android.vending.billing.IInAppBillingService))) {
 | 
			
		||||
                    return ((com.android.vending.billing.IInAppBillingService)iin);
 | 
			
		||||
                }
 | 
			
		||||
                return new com.android.vending.billing.IInAppBillingService.Stub.Proxy(obj);
 | 
			
		||||
            }
 | 
			
		||||
            @Override public android.os.IBinder asBinder()
 | 
			
		||||
            {
 | 
			
		||||
                return this;
 | 
			
		||||
            }
 | 
			
		||||
            @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
 | 
			
		||||
            {
 | 
			
		||||
                switch (code)
 | 
			
		||||
                {
 | 
			
		||||
                    case INTERFACE_TRANSACTION:
 | 
			
		||||
                    {
 | 
			
		||||
                        reply.writeString(DESCRIPTOR);
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_isBillingSupported:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        int _result = this.isBillingSupported(_arg0, _arg1, _arg2);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        reply.writeInt(_result);
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_getSkuDetails:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        android.os.Bundle _arg3;
 | 
			
		||||
                        if ((0!=data.readInt())) {
 | 
			
		||||
                            _arg3 = android.os.Bundle.CREATOR.createFromParcel(data);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _arg3 = null;
 | 
			
		||||
                        }
 | 
			
		||||
                        android.os.Bundle _result = this.getSkuDetails(_arg0, _arg1, _arg2, _arg3);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        if ((_result!=null)) {
 | 
			
		||||
                            reply.writeInt(1);
 | 
			
		||||
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            reply.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_getBuyIntent:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        java.lang.String _arg3;
 | 
			
		||||
                        _arg3 = data.readString();
 | 
			
		||||
                        java.lang.String _arg4;
 | 
			
		||||
                        _arg4 = data.readString();
 | 
			
		||||
                        android.os.Bundle _result = this.getBuyIntent(_arg0, _arg1, _arg2, _arg3, _arg4);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        if ((_result!=null)) {
 | 
			
		||||
                            reply.writeInt(1);
 | 
			
		||||
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            reply.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_getPurchases:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        java.lang.String _arg3;
 | 
			
		||||
                        _arg3 = data.readString();
 | 
			
		||||
                        android.os.Bundle _result = this.getPurchases(_arg0, _arg1, _arg2, _arg3);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        if ((_result!=null)) {
 | 
			
		||||
                            reply.writeInt(1);
 | 
			
		||||
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            reply.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_consumePurchase:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        int _result = this.consumePurchase(_arg0, _arg1, _arg2);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        reply.writeInt(_result);
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_stub:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        int _result = this.stub(_arg0, _arg1, _arg2);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        reply.writeInt(_result);
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_getBuyIntentToReplaceSkus:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.util.List<java.lang.String> _arg2;
 | 
			
		||||
                        _arg2 = data.createStringArrayList();
 | 
			
		||||
                        java.lang.String _arg3;
 | 
			
		||||
                        _arg3 = data.readString();
 | 
			
		||||
                        java.lang.String _arg4;
 | 
			
		||||
                        _arg4 = data.readString();
 | 
			
		||||
                        java.lang.String _arg5;
 | 
			
		||||
                        _arg5 = data.readString();
 | 
			
		||||
                        android.os.Bundle _result = this.getBuyIntentToReplaceSkus(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        if ((_result!=null)) {
 | 
			
		||||
                            reply.writeInt(1);
 | 
			
		||||
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            reply.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_getBuyIntentExtraParams:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        java.lang.String _arg3;
 | 
			
		||||
                        _arg3 = data.readString();
 | 
			
		||||
                        java.lang.String _arg4;
 | 
			
		||||
                        _arg4 = data.readString();
 | 
			
		||||
                        android.os.Bundle _arg5;
 | 
			
		||||
                        if ((0!=data.readInt())) {
 | 
			
		||||
                            _arg5 = android.os.Bundle.CREATOR.createFromParcel(data);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _arg5 = null;
 | 
			
		||||
                        }
 | 
			
		||||
                        android.os.Bundle _result = this.getBuyIntentExtraParams(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        if ((_result!=null)) {
 | 
			
		||||
                            reply.writeInt(1);
 | 
			
		||||
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            reply.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_getPurchaseHistory:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        java.lang.String _arg3;
 | 
			
		||||
                        _arg3 = data.readString();
 | 
			
		||||
                        android.os.Bundle _arg4;
 | 
			
		||||
                        if ((0!=data.readInt())) {
 | 
			
		||||
                            _arg4 = android.os.Bundle.CREATOR.createFromParcel(data);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _arg4 = null;
 | 
			
		||||
                        }
 | 
			
		||||
                        android.os.Bundle _result = this.getPurchaseHistory(_arg0, _arg1, _arg2, _arg3, _arg4);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        if ((_result!=null)) {
 | 
			
		||||
                            reply.writeInt(1);
 | 
			
		||||
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            reply.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                    case TRANSACTION_isBillingSupportedExtraParams:
 | 
			
		||||
                    {
 | 
			
		||||
                        data.enforceInterface(DESCRIPTOR);
 | 
			
		||||
                        int _arg0;
 | 
			
		||||
                        _arg0 = data.readInt();
 | 
			
		||||
                        java.lang.String _arg1;
 | 
			
		||||
                        _arg1 = data.readString();
 | 
			
		||||
                        java.lang.String _arg2;
 | 
			
		||||
                        _arg2 = data.readString();
 | 
			
		||||
                        android.os.Bundle _arg3;
 | 
			
		||||
                        if ((0!=data.readInt())) {
 | 
			
		||||
                            _arg3 = android.os.Bundle.CREATOR.createFromParcel(data);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _arg3 = null;
 | 
			
		||||
                        }
 | 
			
		||||
                        int _result = this.isBillingSupportedExtraParams(_arg0, _arg1, _arg2, _arg3);
 | 
			
		||||
                        reply.writeNoException();
 | 
			
		||||
                        reply.writeInt(_result);
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return super.onTransact(code, data, reply, flags);
 | 
			
		||||
            }
 | 
			
		||||
            private static class Proxy implements com.android.vending.billing.IInAppBillingService
 | 
			
		||||
            {
 | 
			
		||||
                private android.os.IBinder mRemote;
 | 
			
		||||
                Proxy(android.os.IBinder remote)
 | 
			
		||||
                {
 | 
			
		||||
                    mRemote = remote;
 | 
			
		||||
                }
 | 
			
		||||
                @Override public android.os.IBinder asBinder()
 | 
			
		||||
                {
 | 
			
		||||
                    return mRemote;
 | 
			
		||||
                }
 | 
			
		||||
                public java.lang.String getInterfaceDescriptor()
 | 
			
		||||
                {
 | 
			
		||||
                    return DESCRIPTOR;
 | 
			
		||||
                }
 | 
			
		||||
                @Override public int isBillingSupported(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    int _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_isBillingSupported, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        _result = _reply.readInt();
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                /**
 | 
			
		||||
                 * Provides details of a list of SKUs
 | 
			
		||||
                 * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
 | 
			
		||||
                 * with a list JSON strings containing the productId, price, title and description.
 | 
			
		||||
                 * This API can be called with a maximum of 20 SKUs.
 | 
			
		||||
                 * @param apiVersion billing API version that the app is using
 | 
			
		||||
                 * @param packageName the package name of the calling app
 | 
			
		||||
                 * @param type of the in-app items ("inapp" for one-time purchases
 | 
			
		||||
                 *        and "subs" for subscriptions)
 | 
			
		||||
                 * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
 | 
			
		||||
                 * @return Bundle containing the following key-value pairs
 | 
			
		||||
                 *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
                 *                         on failures.
 | 
			
		||||
                 *         "DETAILS_LIST" with a StringArrayList containing purchase information
 | 
			
		||||
                 *                        in JSON format similar to:
 | 
			
		||||
                 *                        '{ "productId" : "exampleSku",
 | 
			
		||||
                 *                           "type" : "inapp",
 | 
			
		||||
                 *                           "price" : "$5.00",
 | 
			
		||||
                 *                           "price_currency": "USD",
 | 
			
		||||
                 *                           "price_amount_micros": 5000000,
 | 
			
		||||
                 *                           "title : "Example Title",
 | 
			
		||||
                 *                           "description" : "This is an example description" }'
 | 
			
		||||
                 */
 | 
			
		||||
                @Override public android.os.Bundle getSkuDetails(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle skusBundle) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Bundle _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        if ((skusBundle!=null)) {
 | 
			
		||||
                            _data.writeInt(1);
 | 
			
		||||
                            skusBundle.writeToParcel(_data, 0);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _data.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_getSkuDetails, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        if ((0!=_reply.readInt())) {
 | 
			
		||||
                            _result = android.os.Bundle.CREATOR.createFromParcel(_reply);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _result = null;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                /**
 | 
			
		||||
                 * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
 | 
			
		||||
                 * the type, a unique purchase token and an optional developer payload.
 | 
			
		||||
                 * @param apiVersion billing API version that the app is using
 | 
			
		||||
                 * @param packageName package name of the calling app
 | 
			
		||||
                 * @param sku the SKU of the in-app item as published in the developer console
 | 
			
		||||
                 * @param type of the in-app item being purchased ("inapp" for one-time purchases
 | 
			
		||||
                 *        and "subs" for subscriptions)
 | 
			
		||||
                 * @param developerPayload optional argument to be sent back with the purchase information
 | 
			
		||||
                 * @return Bundle containing the following key-value pairs
 | 
			
		||||
                 *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
                 *                         on failures.
 | 
			
		||||
                 *         "BUY_INTENT" - PendingIntent to start the purchase flow
 | 
			
		||||
                 *
 | 
			
		||||
                 * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
 | 
			
		||||
                 * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
 | 
			
		||||
                 * If the purchase is successful, the result data will contain the following key-value pairs
 | 
			
		||||
                 *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
 | 
			
		||||
                 *                         codes on failures.
 | 
			
		||||
                 *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
 | 
			
		||||
                 *                                 '{"orderId":"12999763169054705758.1371079406387615",
 | 
			
		||||
                 *                                   "packageName":"com.example.app",
 | 
			
		||||
                 *                                   "productId":"exampleSku",
 | 
			
		||||
                 *                                   "purchaseTime":1345678900000,
 | 
			
		||||
                 *                                   "purchaseToken" : "122333444455555",
 | 
			
		||||
                 *                                   "developerPayload":"example developer payload" }'
 | 
			
		||||
                 *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
 | 
			
		||||
                 *                                  was signed with the private key of the developer
 | 
			
		||||
                 */
 | 
			
		||||
                @Override public android.os.Bundle getBuyIntent(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Bundle _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(sku);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        _data.writeString(developerPayload);
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_getBuyIntent, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        if ((0!=_reply.readInt())) {
 | 
			
		||||
                            _result = android.os.Bundle.CREATOR.createFromParcel(_reply);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _result = null;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                /**
 | 
			
		||||
                 * Returns the current SKUs owned by the user of the type and package name specified along with
 | 
			
		||||
                 * purchase information and a signature of the data to be validated.
 | 
			
		||||
                 * This will return all SKUs that have been purchased in V3 and managed items purchased using
 | 
			
		||||
                 * V1 and V2 that have not been consumed.
 | 
			
		||||
                 * @param apiVersion billing API version that the app is using
 | 
			
		||||
                 * @param packageName package name of the calling app
 | 
			
		||||
                 * @param type of the in-app items being requested ("inapp" for one-time purchases
 | 
			
		||||
                 *        and "subs" for subscriptions)
 | 
			
		||||
                 * @param continuationToken to be set as null for the first call, if the number of owned
 | 
			
		||||
                 *        skus are too many, a continuationToken is returned in the response bundle.
 | 
			
		||||
                 *        This method can be called again with the continuation token to get the next set of
 | 
			
		||||
                 *        owned skus.
 | 
			
		||||
                 * @return Bundle containing the following key-value pairs
 | 
			
		||||
                 *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
                 on failures.
 | 
			
		||||
                 *         "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
 | 
			
		||||
                 *         "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
 | 
			
		||||
                 *         "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
 | 
			
		||||
                 *                                      of the purchase information
 | 
			
		||||
                 *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
 | 
			
		||||
                 *                                      next set of in-app purchases. Only set if the
 | 
			
		||||
                 *                                      user has more owned skus than the current list.
 | 
			
		||||
                 */
 | 
			
		||||
                @Override public android.os.Bundle getPurchases(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Bundle _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        _data.writeString(continuationToken);
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_getPurchases, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        if ((0!=_reply.readInt())) {
 | 
			
		||||
                            _result = android.os.Bundle.CREATOR.createFromParcel(_reply);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _result = null;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                @Override public int consumePurchase(int apiVersion, java.lang.String packageName, java.lang.String purchaseToken) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    int _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(purchaseToken);
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_consumePurchase, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        _result = _reply.readInt();
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                @Override public int stub(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    int _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_stub, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        _result = _reply.readInt();
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                /**
 | 
			
		||||
                 * Returns a pending intent to launch the purchase flow for upgrading or downgrading a
 | 
			
		||||
                 * subscription. The existing owned SKU(s) should be provided along with the new SKU that
 | 
			
		||||
                 * the user is upgrading or downgrading to.
 | 
			
		||||
                 * @param apiVersion billing API version that the app is using, must be 5 or later
 | 
			
		||||
                 * @param packageName package name of the calling app
 | 
			
		||||
                 * @param oldSkus the SKU(s) that the user is upgrading or downgrading from,
 | 
			
		||||
                 *        if null or empty this method will behave like {@link #getBuyIntent}
 | 
			
		||||
                 * @param newSku the SKU that the user is upgrading or downgrading to
 | 
			
		||||
                 * @param type of the item being purchased, currently must be "subs"
 | 
			
		||||
                 * @param developerPayload optional argument to be sent back with the purchase information
 | 
			
		||||
                 * @return Bundle containing the following key-value pairs
 | 
			
		||||
                 *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
                 *                         on failures.
 | 
			
		||||
                 *         "BUY_INTENT" - PendingIntent to start the purchase flow
 | 
			
		||||
                 *
 | 
			
		||||
                 * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
 | 
			
		||||
                 * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
 | 
			
		||||
                 * If the purchase is successful, the result data will contain the following key-value pairs
 | 
			
		||||
                 *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
 | 
			
		||||
                 *                         codes on failures.
 | 
			
		||||
                 *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
 | 
			
		||||
                 *                                 '{"orderId":"12999763169054705758.1371079406387615",
 | 
			
		||||
                 *                                   "packageName":"com.example.app",
 | 
			
		||||
                 *                                   "productId":"exampleSku",
 | 
			
		||||
                 *                                   "purchaseTime":1345678900000,
 | 
			
		||||
                 *                                   "purchaseToken" : "122333444455555",
 | 
			
		||||
                 *                                   "developerPayload":"example developer payload" }'
 | 
			
		||||
                 *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
 | 
			
		||||
                 *                                  was signed with the private key of the developer
 | 
			
		||||
                 */
 | 
			
		||||
                @Override public android.os.Bundle getBuyIntentToReplaceSkus(int apiVersion, java.lang.String packageName, java.util.List<java.lang.String> oldSkus, java.lang.String newSku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Bundle _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeStringList(oldSkus);
 | 
			
		||||
                        _data.writeString(newSku);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        _data.writeString(developerPayload);
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_getBuyIntentToReplaceSkus, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        if ((0!=_reply.readInt())) {
 | 
			
		||||
                            _result = android.os.Bundle.CREATOR.createFromParcel(_reply);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _result = null;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                /**
 | 
			
		||||
                 * Returns a pending intent to launch the purchase flow for an in-app item. This method is
 | 
			
		||||
                 * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams}
 | 
			
		||||
                 * parameter. This parameter is a Bundle of optional keys and values that affect the
 | 
			
		||||
                 * operation of the method.
 | 
			
		||||
                 * @param apiVersion billing API version that the app is using, must be 6 or later
 | 
			
		||||
                 * @param packageName package name of the calling app
 | 
			
		||||
                 * @param sku the SKU of the in-app item as published in the developer console
 | 
			
		||||
                 * @param type of the in-app item being purchased ("inapp" for one-time purchases
 | 
			
		||||
                 *        and "subs" for subscriptions)
 | 
			
		||||
                 * @param developerPayload optional argument to be sent back with the purchase information
 | 
			
		||||
                 * @extraParams a Bundle with the following optional keys:
 | 
			
		||||
                 *        "skusToReplace" - List<String> - an optional list of SKUs that the user is
 | 
			
		||||
                 *                          upgrading or downgrading from.
 | 
			
		||||
                 *                          Pass this field if the purchase is upgrading or downgrading
 | 
			
		||||
                 *                          existing subscriptions.
 | 
			
		||||
                 *                          The specified SKUs are replaced with the SKUs that the user is
 | 
			
		||||
                 *                          purchasing. Google Play replaces the specified SKUs at the start of
 | 
			
		||||
                 *                          the next billing cycle.
 | 
			
		||||
                 * "replaceSkusProration" - Boolean - whether the user should be credited for any unused
 | 
			
		||||
                 *                          subscription time on the SKUs they are upgrading or downgrading.
 | 
			
		||||
                 *                          If you set this field to true, Google Play swaps out the old SKUs
 | 
			
		||||
                 *                          and credits the user with the unused value of their subscription
 | 
			
		||||
                 *                          time on a pro-rated basis.
 | 
			
		||||
                 *                          Google Play applies this credit to the new subscription, and does
 | 
			
		||||
                 *                          not begin billing the user for the new subscription until after
 | 
			
		||||
                 *                          the credit is used up.
 | 
			
		||||
                 *                          If you set this field to false, the user does not receive credit for
 | 
			
		||||
                 *                          any unused subscription time and the recurrence date does not
 | 
			
		||||
                 *                          change.
 | 
			
		||||
                 *                          Default value is true. Ignored if you do not pass skusToReplace.
 | 
			
		||||
                 *            "accountId" - String - an optional obfuscated string that is uniquely
 | 
			
		||||
                 *                          associated with the user's account in your app.
 | 
			
		||||
                 *                          If you pass this value, Google Play can use it to detect irregular
 | 
			
		||||
                 *                          activity, such as many devices making purchases on the same
 | 
			
		||||
                 *                          account in a short period of time.
 | 
			
		||||
                 *                          Do not use the developer ID or the user's Google ID for this field.
 | 
			
		||||
                 *                          In addition, this field should not contain the user's ID in
 | 
			
		||||
                 *                          cleartext.
 | 
			
		||||
                 *                          We recommend that you use a one-way hash to generate a string from
 | 
			
		||||
                 *                          the user's ID, and store the hashed string in this field.
 | 
			
		||||
                 *                   "vr" - Boolean - an optional flag indicating whether the returned intent
 | 
			
		||||
                 *                          should start a VR purchase flow. The apiVersion must also be 7 or
 | 
			
		||||
                 *                          later to use this flag.
 | 
			
		||||
                 */
 | 
			
		||||
                @Override public android.os.Bundle getBuyIntentExtraParams(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload, android.os.Bundle extraParams) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Bundle _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(sku);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        _data.writeString(developerPayload);
 | 
			
		||||
                        if ((extraParams!=null)) {
 | 
			
		||||
                            _data.writeInt(1);
 | 
			
		||||
                            extraParams.writeToParcel(_data, 0);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _data.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_getBuyIntentExtraParams, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        if ((0!=_reply.readInt())) {
 | 
			
		||||
                            _result = android.os.Bundle.CREATOR.createFromParcel(_reply);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _result = null;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                /**
 | 
			
		||||
                 * Returns the most recent purchase made by the user for each SKU, even if that purchase is
 | 
			
		||||
                 * expired, canceled, or consumed.
 | 
			
		||||
                 * @param apiVersion billing API version that the app is using, must be 6 or later
 | 
			
		||||
                 * @param packageName package name of the calling app
 | 
			
		||||
                 * @param type of the in-app items being requested ("inapp" for one-time purchases
 | 
			
		||||
                 *        and "subs" for subscriptions)
 | 
			
		||||
                 * @param continuationToken to be set as null for the first call, if the number of owned
 | 
			
		||||
                 *        skus is too large, a continuationToken is returned in the response bundle.
 | 
			
		||||
                 *        This method can be called again with the continuation token to get the next set of
 | 
			
		||||
                 *        owned skus.
 | 
			
		||||
                 * @param extraParams a Bundle with extra params that would be appended into http request
 | 
			
		||||
                 *        query string. Not used at this moment. Reserved for future functionality.
 | 
			
		||||
                 * @return Bundle containing the following key-value pairs
 | 
			
		||||
                 *         "RESPONSE_CODE" with int value: RESULT_OK(0) if success,
 | 
			
		||||
                 *         {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures.
 | 
			
		||||
                 *
 | 
			
		||||
                 *         "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs
 | 
			
		||||
                 *         "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information
 | 
			
		||||
                 *         "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures
 | 
			
		||||
                 *                                      of the purchase information
 | 
			
		||||
                 *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
 | 
			
		||||
                 *                                      next set of in-app purchases. Only set if the
 | 
			
		||||
                 *                                      user has more owned skus than the current list.
 | 
			
		||||
                 */
 | 
			
		||||
                @Override public android.os.Bundle getPurchaseHistory(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken, android.os.Bundle extraParams) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Bundle _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        _data.writeString(continuationToken);
 | 
			
		||||
                        if ((extraParams!=null)) {
 | 
			
		||||
                            _data.writeInt(1);
 | 
			
		||||
                            extraParams.writeToParcel(_data, 0);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _data.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_getPurchaseHistory, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        if ((0!=_reply.readInt())) {
 | 
			
		||||
                            _result = android.os.Bundle.CREATOR.createFromParcel(_reply);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _result = null;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
                @Override public int isBillingSupportedExtraParams(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle extraParams) throws android.os.RemoteException
 | 
			
		||||
                {
 | 
			
		||||
                    android.os.Parcel _data = android.os.Parcel.obtain();
 | 
			
		||||
                    android.os.Parcel _reply = android.os.Parcel.obtain();
 | 
			
		||||
                    int _result;
 | 
			
		||||
                    try {
 | 
			
		||||
                        _data.writeInterfaceToken(DESCRIPTOR);
 | 
			
		||||
                        _data.writeInt(apiVersion);
 | 
			
		||||
                        _data.writeString(packageName);
 | 
			
		||||
                        _data.writeString(type);
 | 
			
		||||
                        if ((extraParams!=null)) {
 | 
			
		||||
                            _data.writeInt(1);
 | 
			
		||||
                            extraParams.writeToParcel(_data, 0);
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            _data.writeInt(0);
 | 
			
		||||
                        }
 | 
			
		||||
                        mRemote.transact(Stub.TRANSACTION_isBillingSupportedExtraParams, _data, _reply, 0);
 | 
			
		||||
                        _reply.readException();
 | 
			
		||||
                        _result = _reply.readInt();
 | 
			
		||||
                    }
 | 
			
		||||
                    finally {
 | 
			
		||||
                        _reply.recycle();
 | 
			
		||||
                        _data.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return _result;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            static final int TRANSACTION_isBillingSupported = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
 | 
			
		||||
            static final int TRANSACTION_getSkuDetails = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
 | 
			
		||||
            static final int TRANSACTION_getBuyIntent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
 | 
			
		||||
            static final int TRANSACTION_getPurchases = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
 | 
			
		||||
            static final int TRANSACTION_consumePurchase = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
 | 
			
		||||
            static final int TRANSACTION_stub = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
 | 
			
		||||
            static final int TRANSACTION_getBuyIntentToReplaceSkus = (android.os.IBinder.FIRST_CALL_TRANSACTION + 6);
 | 
			
		||||
            static final int TRANSACTION_getBuyIntentExtraParams = (android.os.IBinder.FIRST_CALL_TRANSACTION + 7);
 | 
			
		||||
            static final int TRANSACTION_getPurchaseHistory = (android.os.IBinder.FIRST_CALL_TRANSACTION + 8);
 | 
			
		||||
            static final int TRANSACTION_isBillingSupportedExtraParams = (android.os.IBinder.FIRST_CALL_TRANSACTION + 9);
 | 
			
		||||
        }
 | 
			
		||||
        public int isBillingSupported(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException;
 | 
			
		||||
        /**
 | 
			
		||||
         * Provides details of a list of SKUs
 | 
			
		||||
         * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
 | 
			
		||||
         * with a list JSON strings containing the productId, price, title and description.
 | 
			
		||||
         * This API can be called with a maximum of 20 SKUs.
 | 
			
		||||
         * @param apiVersion billing API version that the app is using
 | 
			
		||||
         * @param packageName the package name of the calling app
 | 
			
		||||
         * @param type of the in-app items ("inapp" for one-time purchases
 | 
			
		||||
         *        and "subs" for subscriptions)
 | 
			
		||||
         * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
 | 
			
		||||
         * @return Bundle containing the following key-value pairs
 | 
			
		||||
         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
         *                         on failures.
 | 
			
		||||
         *         "DETAILS_LIST" with a StringArrayList containing purchase information
 | 
			
		||||
         *                        in JSON format similar to:
 | 
			
		||||
         *                        '{ "productId" : "exampleSku",
 | 
			
		||||
         *                           "type" : "inapp",
 | 
			
		||||
         *                           "price" : "$5.00",
 | 
			
		||||
         *                           "price_currency": "USD",
 | 
			
		||||
         *                           "price_amount_micros": 5000000,
 | 
			
		||||
         *                           "title : "Example Title",
 | 
			
		||||
         *                           "description" : "This is an example description" }'
 | 
			
		||||
         */
 | 
			
		||||
        public android.os.Bundle getSkuDetails(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle skusBundle) throws android.os.RemoteException;
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
 | 
			
		||||
         * the type, a unique purchase token and an optional developer payload.
 | 
			
		||||
         * @param apiVersion billing API version that the app is using
 | 
			
		||||
         * @param packageName package name of the calling app
 | 
			
		||||
         * @param sku the SKU of the in-app item as published in the developer console
 | 
			
		||||
         * @param type of the in-app item being purchased ("inapp" for one-time purchases
 | 
			
		||||
         *        and "subs" for subscriptions)
 | 
			
		||||
         * @param developerPayload optional argument to be sent back with the purchase information
 | 
			
		||||
         * @return Bundle containing the following key-value pairs
 | 
			
		||||
         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
         *                         on failures.
 | 
			
		||||
         *         "BUY_INTENT" - PendingIntent to start the purchase flow
 | 
			
		||||
         *
 | 
			
		||||
         * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
 | 
			
		||||
         * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
 | 
			
		||||
         * If the purchase is successful, the result data will contain the following key-value pairs
 | 
			
		||||
         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
 | 
			
		||||
         *                         codes on failures.
 | 
			
		||||
         *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
 | 
			
		||||
         *                                 '{"orderId":"12999763169054705758.1371079406387615",
 | 
			
		||||
         *                                   "packageName":"com.example.app",
 | 
			
		||||
         *                                   "productId":"exampleSku",
 | 
			
		||||
         *                                   "purchaseTime":1345678900000,
 | 
			
		||||
         *                                   "purchaseToken" : "122333444455555",
 | 
			
		||||
         *                                   "developerPayload":"example developer payload" }'
 | 
			
		||||
         *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
 | 
			
		||||
         *                                  was signed with the private key of the developer
 | 
			
		||||
         */
 | 
			
		||||
        public android.os.Bundle getBuyIntent(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException;
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns the current SKUs owned by the user of the type and package name specified along with
 | 
			
		||||
         * purchase information and a signature of the data to be validated.
 | 
			
		||||
         * This will return all SKUs that have been purchased in V3 and managed items purchased using
 | 
			
		||||
         * V1 and V2 that have not been consumed.
 | 
			
		||||
         * @param apiVersion billing API version that the app is using
 | 
			
		||||
         * @param packageName package name of the calling app
 | 
			
		||||
         * @param type of the in-app items being requested ("inapp" for one-time purchases
 | 
			
		||||
         *        and "subs" for subscriptions)
 | 
			
		||||
         * @param continuationToken to be set as null for the first call, if the number of owned
 | 
			
		||||
         *        skus are too many, a continuationToken is returned in the response bundle.
 | 
			
		||||
         *        This method can be called again with the continuation token to get the next set of
 | 
			
		||||
         *        owned skus.
 | 
			
		||||
         * @return Bundle containing the following key-value pairs
 | 
			
		||||
         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
         on failures.
 | 
			
		||||
         *         "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
 | 
			
		||||
         *         "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
 | 
			
		||||
         *         "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
 | 
			
		||||
         *                                      of the purchase information
 | 
			
		||||
         *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
 | 
			
		||||
         *                                      next set of in-app purchases. Only set if the
 | 
			
		||||
         *                                      user has more owned skus than the current list.
 | 
			
		||||
         */
 | 
			
		||||
        public android.os.Bundle getPurchases(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken) throws android.os.RemoteException;
 | 
			
		||||
        public int consumePurchase(int apiVersion, java.lang.String packageName, java.lang.String purchaseToken) throws android.os.RemoteException;
 | 
			
		||||
        public int stub(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException;
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns a pending intent to launch the purchase flow for upgrading or downgrading a
 | 
			
		||||
         * subscription. The existing owned SKU(s) should be provided along with the new SKU that
 | 
			
		||||
         * the user is upgrading or downgrading to.
 | 
			
		||||
         * @param apiVersion billing API version that the app is using, must be 5 or later
 | 
			
		||||
         * @param packageName package name of the calling app
 | 
			
		||||
         * @param oldSkus the SKU(s) that the user is upgrading or downgrading from,
 | 
			
		||||
         *        if null or empty this method will behave like {@link #getBuyIntent}
 | 
			
		||||
         * @param newSku the SKU that the user is upgrading or downgrading to
 | 
			
		||||
         * @param type of the item being purchased, currently must be "subs"
 | 
			
		||||
         * @param developerPayload optional argument to be sent back with the purchase information
 | 
			
		||||
         * @return Bundle containing the following key-value pairs
 | 
			
		||||
         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes
 | 
			
		||||
         *                         on failures.
 | 
			
		||||
         *         "BUY_INTENT" - PendingIntent to start the purchase flow
 | 
			
		||||
         *
 | 
			
		||||
         * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
 | 
			
		||||
         * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
 | 
			
		||||
         * If the purchase is successful, the result data will contain the following key-value pairs
 | 
			
		||||
         *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response
 | 
			
		||||
         *                         codes on failures.
 | 
			
		||||
         *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
 | 
			
		||||
         *                                 '{"orderId":"12999763169054705758.1371079406387615",
 | 
			
		||||
         *                                   "packageName":"com.example.app",
 | 
			
		||||
         *                                   "productId":"exampleSku",
 | 
			
		||||
         *                                   "purchaseTime":1345678900000,
 | 
			
		||||
         *                                   "purchaseToken" : "122333444455555",
 | 
			
		||||
         *                                   "developerPayload":"example developer payload" }'
 | 
			
		||||
         *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
 | 
			
		||||
         *                                  was signed with the private key of the developer
 | 
			
		||||
         */
 | 
			
		||||
        public android.os.Bundle getBuyIntentToReplaceSkus(int apiVersion, java.lang.String packageName, java.util.List<java.lang.String> oldSkus, java.lang.String newSku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException;
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns a pending intent to launch the purchase flow for an in-app item. This method is
 | 
			
		||||
         * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams}
 | 
			
		||||
         * parameter. This parameter is a Bundle of optional keys and values that affect the
 | 
			
		||||
         * operation of the method.
 | 
			
		||||
         * @param apiVersion billing API version that the app is using, must be 6 or later
 | 
			
		||||
         * @param packageName package name of the calling app
 | 
			
		||||
         * @param sku the SKU of the in-app item as published in the developer console
 | 
			
		||||
         * @param type of the in-app item being purchased ("inapp" for one-time purchases
 | 
			
		||||
         *        and "subs" for subscriptions)
 | 
			
		||||
         * @param developerPayload optional argument to be sent back with the purchase information
 | 
			
		||||
         * @extraParams a Bundle with the following optional keys:
 | 
			
		||||
         *        "skusToReplace" - List<String> - an optional list of SKUs that the user is
 | 
			
		||||
         *                          upgrading or downgrading from.
 | 
			
		||||
         *                          Pass this field if the purchase is upgrading or downgrading
 | 
			
		||||
         *                          existing subscriptions.
 | 
			
		||||
         *                          The specified SKUs are replaced with the SKUs that the user is
 | 
			
		||||
         *                          purchasing. Google Play replaces the specified SKUs at the start of
 | 
			
		||||
         *                          the next billing cycle.
 | 
			
		||||
         * "replaceSkusProration" - Boolean - whether the user should be credited for any unused
 | 
			
		||||
         *                          subscription time on the SKUs they are upgrading or downgrading.
 | 
			
		||||
         *                          If you set this field to true, Google Play swaps out the old SKUs
 | 
			
		||||
         *                          and credits the user with the unused value of their subscription
 | 
			
		||||
         *                          time on a pro-rated basis.
 | 
			
		||||
         *                          Google Play applies this credit to the new subscription, and does
 | 
			
		||||
         *                          not begin billing the user for the new subscription until after
 | 
			
		||||
         *                          the credit is used up.
 | 
			
		||||
         *                          If you set this field to false, the user does not receive credit for
 | 
			
		||||
         *                          any unused subscription time and the recurrence date does not
 | 
			
		||||
         *                          change.
 | 
			
		||||
         *                          Default value is true. Ignored if you do not pass skusToReplace.
 | 
			
		||||
         *            "accountId" - String - an optional obfuscated string that is uniquely
 | 
			
		||||
         *                          associated with the user's account in your app.
 | 
			
		||||
         *                          If you pass this value, Google Play can use it to detect irregular
 | 
			
		||||
         *                          activity, such as many devices making purchases on the same
 | 
			
		||||
         *                          account in a short period of time.
 | 
			
		||||
         *                          Do not use the developer ID or the user's Google ID for this field.
 | 
			
		||||
         *                          In addition, this field should not contain the user's ID in
 | 
			
		||||
         *                          cleartext.
 | 
			
		||||
         *                          We recommend that you use a one-way hash to generate a string from
 | 
			
		||||
         *                          the user's ID, and store the hashed string in this field.
 | 
			
		||||
         *                   "vr" - Boolean - an optional flag indicating whether the returned intent
 | 
			
		||||
         *                          should start a VR purchase flow. The apiVersion must also be 7 or
 | 
			
		||||
         *                          later to use this flag.
 | 
			
		||||
         */
 | 
			
		||||
        public android.os.Bundle getBuyIntentExtraParams(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload, android.os.Bundle extraParams) throws android.os.RemoteException;
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns the most recent purchase made by the user for each SKU, even if that purchase is
 | 
			
		||||
         * expired, canceled, or consumed.
 | 
			
		||||
         * @param apiVersion billing API version that the app is using, must be 6 or later
 | 
			
		||||
         * @param packageName package name of the calling app
 | 
			
		||||
         * @param type of the in-app items being requested ("inapp" for one-time purchases
 | 
			
		||||
         *        and "subs" for subscriptions)
 | 
			
		||||
         * @param continuationToken to be set as null for the first call, if the number of owned
 | 
			
		||||
         *        skus is too large, a continuationToken is returned in the response bundle.
 | 
			
		||||
         *        This method can be called again with the continuation token to get the next set of
 | 
			
		||||
         *        owned skus.
 | 
			
		||||
         * @param extraParams a Bundle with extra params that would be appended into http request
 | 
			
		||||
         *        query string. Not used at this moment. Reserved for future functionality.
 | 
			
		||||
         * @return Bundle containing the following key-value pairs
 | 
			
		||||
         *         "RESPONSE_CODE" with int value: RESULT_OK(0) if success,
 | 
			
		||||
         *         {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures.
 | 
			
		||||
         *
 | 
			
		||||
         *         "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs
 | 
			
		||||
         *         "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information
 | 
			
		||||
         *         "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures
 | 
			
		||||
         *                                      of the purchase information
 | 
			
		||||
         *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
 | 
			
		||||
         *                                      next set of in-app purchases. Only set if the
 | 
			
		||||
         *                                      user has more owned skus than the current list.
 | 
			
		||||
         */
 | 
			
		||||
        public android.os.Bundle getPurchaseHistory(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken, android.os.Bundle extraParams) throws android.os.RemoteException;
 | 
			
		||||
        public int isBillingSupportedExtraParams(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle extraParams) throws android.os.RemoteException;
 | 
			
		||||
    }
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,16 +0,0 @@
 | 
			
		||||
package com.juce;
 | 
			
		||||
 | 
			
		||||
import com.google.firebase.iid.*;
 | 
			
		||||
 | 
			
		||||
public final class JuceFirebaseInstanceIdService extends FirebaseInstanceIdService
 | 
			
		||||
{
 | 
			
		||||
    private native void firebaseInstanceIdTokenRefreshed (String token);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onTokenRefresh()
 | 
			
		||||
    {
 | 
			
		||||
        String token = FirebaseInstanceId.getInstance().getToken();
 | 
			
		||||
 | 
			
		||||
        firebaseInstanceIdTokenRefreshed (token);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,35 +0,0 @@
 | 
			
		||||
package com.juce;
 | 
			
		||||
 | 
			
		||||
import com.google.firebase.messaging.*;
 | 
			
		||||
 | 
			
		||||
public final class JuceFirebaseMessagingService extends FirebaseMessagingService
 | 
			
		||||
{
 | 
			
		||||
    private native void firebaseRemoteMessageReceived (RemoteMessage message);
 | 
			
		||||
    private native void firebaseRemoteMessagesDeleted();
 | 
			
		||||
    private native void firebaseRemoteMessageSent (String messageId);
 | 
			
		||||
    private native void firebaseRemoteMessageSendError (String messageId, String error);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onMessageReceived (RemoteMessage message)
 | 
			
		||||
    {
 | 
			
		||||
        firebaseRemoteMessageReceived (message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onDeletedMessages()
 | 
			
		||||
    {
 | 
			
		||||
        firebaseRemoteMessagesDeleted();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onMessageSent (String messageId)
 | 
			
		||||
    {
 | 
			
		||||
        firebaseRemoteMessageSent (messageId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onSendError (String messageId, Exception e)
 | 
			
		||||
    {
 | 
			
		||||
        firebaseRemoteMessageSendError (messageId, e.toString());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								modules/juce_core/native/java/README.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								modules/juce_core/native/java/README.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
The Java code in the module's native/java subfolders have been used to generate
 | 
			
		||||
dex byte-code in various places in the JUCE framework. These are the steps
 | 
			
		||||
required to re-generate the dex byte-code from any Java source code inside the
 | 
			
		||||
native/java subfolders:
 | 
			
		||||
 | 
			
		||||
1. Create a new JUCE android project with the minimal sdk version which is
 | 
			
		||||
required for the Java source code you wish to compile.
 | 
			
		||||
 | 
			
		||||
2. If you are creating byte-code for new .java files, move the new files into
 | 
			
		||||
the native/javacore/app folder of the module, or create one if it doesn't
 | 
			
		||||
exist. Remember that .java files need to be in nested sub-folders which
 | 
			
		||||
resemble their package, i.e. a Java class com.roli.juce.HelloWorld.java
 | 
			
		||||
should be in the module's native/javacore/app/com/roli/juce folder.
 | 
			
		||||
If you wish to modify existing .java files in the JUCE modules then just rename
 | 
			
		||||
native/java to native/javacore.
 | 
			
		||||
 | 
			
		||||
3. Build your project with AS and run. The app will now use the source code in
 | 
			
		||||
the folder created in step 2 so you can debug your Java code this way.
 | 
			
		||||
 | 
			
		||||
4. Once everything is working rebuild your app in release mode.
 | 
			
		||||
 | 
			
		||||
5. Go to your app's Builds/Android folder. Inside there you will find
 | 
			
		||||
build/intermediates/javac/release_Release/compileRelease_ReleaseJavaWithJavac/classes. 
 | 
			
		||||
Inside of that folder, you will find all your Java byte-code compiled classes.
 | 
			
		||||
Remove any classes that you are not interested in (typically you'll find
 | 
			
		||||
Java.class, JuceApp.class and JuceSharingContentProvider.class which you will
 | 
			
		||||
probably want to remove).
 | 
			
		||||
 | 
			
		||||
6. Inside of app/build/intermediates/classes/release_/release execute the
 | 
			
		||||
following dx command:
 | 
			
		||||
 | 
			
		||||
    <path-to-your-android-sdk>/build-tools/<latest-build-tool-version>/dx --dex --verbose --min-sdk-version=<your-min-sdk-of-your-classes> --output /tmp/JavaDexByteCode.dex .
 | 
			
		||||
 | 
			
		||||
    (Replace <your-min-sdk-of-your-classes> with the minimal sdk version you used in step 1.)
 | 
			
		||||
   
 | 
			
		||||
7. gzip the output:
 | 
			
		||||
 | 
			
		||||
    gzip /tmp/JavaDexByteCode.dex
 | 
			
		||||
 | 
			
		||||
8. The output /tmp/JavaDexByteCode.dex.gz is now the byte code that can be
 | 
			
		||||
included into JUCE. You can use the Projucer's BinaryData generator
 | 
			
		||||
functionality to get this into a convenient char array like form.
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
package com.roli.juce;
 | 
			
		||||
 | 
			
		||||
import android.app.DialogFragment;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
 | 
			
		||||
public class FragmentOverlay extends DialogFragment
 | 
			
		||||
{
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreate (Bundle state)
 | 
			
		||||
    {
 | 
			
		||||
        super.onCreate (state);
 | 
			
		||||
        cppThis = getArguments ().getLong ("cppThis");
 | 
			
		||||
 | 
			
		||||
        if (cppThis != 0)
 | 
			
		||||
            onCreateNative (cppThis, state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onStart ()
 | 
			
		||||
    {
 | 
			
		||||
        super.onStart ();
 | 
			
		||||
 | 
			
		||||
        if (cppThis != 0)
 | 
			
		||||
            onStartNative (cppThis);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onRequestPermissionsResult (int requestCode,
 | 
			
		||||
                                            String[] permissions,
 | 
			
		||||
                                            int[] grantResults)
 | 
			
		||||
    {
 | 
			
		||||
        if (cppThis != 0)
 | 
			
		||||
            onRequestPermissionsResultNative (cppThis, requestCode,
 | 
			
		||||
                    permissions, grantResults);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onActivityResult (int requestCode, int resultCode, Intent data)
 | 
			
		||||
    {
 | 
			
		||||
        if (cppThis != 0)
 | 
			
		||||
            onActivityResultNative (cppThis, requestCode, resultCode, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void close ()
 | 
			
		||||
    {
 | 
			
		||||
        cppThis = 0;
 | 
			
		||||
        dismiss ();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    private long cppThis = 0;
 | 
			
		||||
 | 
			
		||||
    private native void onActivityResultNative (long myself, int requestCode, int resultCode, Intent data);
 | 
			
		||||
    private native void onCreateNative (long myself, Bundle state);
 | 
			
		||||
    private native void onStartNative (long myself);
 | 
			
		||||
    private native void onRequestPermissionsResultNative (long myself, int requestCode,
 | 
			
		||||
                                                          String[] permissions, int[] grantResults);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,407 @@
 | 
			
		||||
package com.roli.juce;
 | 
			
		||||
 | 
			
		||||
import java.lang.Runnable;
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.net.HttpURLConnection;
 | 
			
		||||
import java.util.concurrent.CancellationException;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Callable;
 | 
			
		||||
import java.util.concurrent.locks.ReentrantLock;
 | 
			
		||||
import java.util.concurrent.atomic.*;
 | 
			
		||||
 | 
			
		||||
public class JuceHTTPStream
 | 
			
		||||
{
 | 
			
		||||
    public JuceHTTPStream(String address, boolean isPostToUse, byte[] postDataToUse,
 | 
			
		||||
                          String headersToUse, int timeOutMsToUse,
 | 
			
		||||
                          int[] statusCodeToUse, StringBuffer responseHeadersToUse,
 | 
			
		||||
                          int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        isPost = isPostToUse;
 | 
			
		||||
        postData = postDataToUse;
 | 
			
		||||
        headers = headersToUse;
 | 
			
		||||
        timeOutMs = timeOutMsToUse;
 | 
			
		||||
        statusCode = statusCodeToUse;
 | 
			
		||||
        responseHeaders = responseHeadersToUse;
 | 
			
		||||
        totalLength = -1;
 | 
			
		||||
        numRedirectsToFollow = numRedirectsToFollowToUse;
 | 
			
		||||
        httpRequestCmd = httpRequestCmdToUse;
 | 
			
		||||
 | 
			
		||||
        connection = createConnection(address, isPost, postData, headers, timeOutMs, httpRequestCmd);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static final JuceHTTPStream createHTTPStream(String address, boolean isPost, byte[] postData,
 | 
			
		||||
                                                        String headers, int timeOutMs, int[] statusCode,
 | 
			
		||||
                                                        StringBuffer responseHeaders, int numRedirectsToFollow,
 | 
			
		||||
                                                        String httpRequestCmd)
 | 
			
		||||
    {
 | 
			
		||||
        // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
 | 
			
		||||
        if (timeOutMs < 0)
 | 
			
		||||
            timeOutMs = 0;
 | 
			
		||||
        else if (timeOutMs == 0)
 | 
			
		||||
            timeOutMs = 30000;
 | 
			
		||||
 | 
			
		||||
        for (; ; )
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                JuceHTTPStream httpStream = new JuceHTTPStream(address, isPost, postData, headers,
 | 
			
		||||
                        timeOutMs, statusCode, responseHeaders,
 | 
			
		||||
                        numRedirectsToFollow, httpRequestCmd);
 | 
			
		||||
 | 
			
		||||
                return httpStream;
 | 
			
		||||
            } catch (Throwable e)
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final HttpURLConnection createConnection(String address, boolean isPost, byte[] postData,
 | 
			
		||||
                                                     String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            newConnection.setInstanceFollowRedirects(false);
 | 
			
		||||
            newConnection.setConnectTimeout(timeOutMs);
 | 
			
		||||
            newConnection.setReadTimeout(timeOutMs);
 | 
			
		||||
 | 
			
		||||
            // 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.
 | 
			
		||||
            // So convert headers string to an array, with an element for each line
 | 
			
		||||
            String headerLines[] = headers.split("\\n");
 | 
			
		||||
 | 
			
		||||
            // Set request headers
 | 
			
		||||
            for (int i = 0; i < headerLines.length; ++i)
 | 
			
		||||
            {
 | 
			
		||||
                int pos = headerLines[i].indexOf(":");
 | 
			
		||||
 | 
			
		||||
                if (pos > 0 && pos < headerLines[i].length())
 | 
			
		||||
                {
 | 
			
		||||
                    String field = headerLines[i].substring(0, pos);
 | 
			
		||||
                    String value = headerLines[i].substring(pos + 1);
 | 
			
		||||
 | 
			
		||||
                    if (value.length() > 0)
 | 
			
		||||
                        newConnection.setRequestProperty(field, value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newConnection.setRequestMethod(httpRequestCmd);
 | 
			
		||||
 | 
			
		||||
            if (isPost)
 | 
			
		||||
            {
 | 
			
		||||
                newConnection.setDoOutput(true);
 | 
			
		||||
 | 
			
		||||
                if (postData != null)
 | 
			
		||||
                {
 | 
			
		||||
                    OutputStream out = newConnection.getOutputStream();
 | 
			
		||||
                    out.write(postData);
 | 
			
		||||
                    out.flush();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return newConnection;
 | 
			
		||||
        } catch (Throwable e)
 | 
			
		||||
        {
 | 
			
		||||
            newConnection.disconnect();
 | 
			
		||||
            throw new IOException("Connection error");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final InputStream getCancellableStream(final boolean isInput) throws ExecutionException
 | 
			
		||||
    {
 | 
			
		||||
        synchronized (createFutureLock)
 | 
			
		||||
        {
 | 
			
		||||
            if (hasBeenCancelled.get())
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            streamFuture = executor.submit(new Callable<BufferedInputStream>()
 | 
			
		||||
            {
 | 
			
		||||
                @Override
 | 
			
		||||
                public BufferedInputStream call() throws IOException
 | 
			
		||||
                {
 | 
			
		||||
                    return new BufferedInputStream(isInput ? connection.getInputStream()
 | 
			
		||||
                            : connection.getErrorStream());
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            return streamFuture.get();
 | 
			
		||||
        } catch (InterruptedException e)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        } catch (CancellationException e)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final boolean connect()
 | 
			
		||||
    {
 | 
			
		||||
        boolean result = false;
 | 
			
		||||
        int numFollowedRedirects = 0;
 | 
			
		||||
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            result = doConnect();
 | 
			
		||||
 | 
			
		||||
            if (!result)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (++numFollowedRedirects > numRedirectsToFollow)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            int status = statusCode[0];
 | 
			
		||||
 | 
			
		||||
            if (status == 301 || status == 302 || status == 303 || status == 307)
 | 
			
		||||
            {
 | 
			
		||||
                // Assumes only one occurrence of "Location"
 | 
			
		||||
                int pos1 = responseHeaders.indexOf("Location:") + 10;
 | 
			
		||||
                int pos2 = responseHeaders.indexOf("\n", pos1);
 | 
			
		||||
 | 
			
		||||
                if (pos2 > pos1)
 | 
			
		||||
                {
 | 
			
		||||
                    String currentLocation = connection.getURL().toString();
 | 
			
		||||
                    String newLocation = responseHeaders.substring(pos1, pos2);
 | 
			
		||||
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        // Handle newLocation whether it's absolute or relative
 | 
			
		||||
                        URL baseUrl = new URL(currentLocation);
 | 
			
		||||
                        URL newUrl = new URL(baseUrl, newLocation);
 | 
			
		||||
                        String transformedNewLocation = newUrl.toString();
 | 
			
		||||
 | 
			
		||||
                        if (transformedNewLocation != currentLocation)
 | 
			
		||||
                        {
 | 
			
		||||
                            // Clear responseHeaders before next iteration
 | 
			
		||||
                            responseHeaders.delete(0, responseHeaders.length());
 | 
			
		||||
 | 
			
		||||
                            synchronized (createStreamLock)
 | 
			
		||||
                            {
 | 
			
		||||
                                if (hasBeenCancelled.get())
 | 
			
		||||
                                    return false;
 | 
			
		||||
 | 
			
		||||
                                connection.disconnect();
 | 
			
		||||
 | 
			
		||||
                                try
 | 
			
		||||
                                {
 | 
			
		||||
                                    connection = createConnection(transformedNewLocation, isPost,
 | 
			
		||||
                                            postData, headers, timeOutMs,
 | 
			
		||||
                                            httpRequestCmd);
 | 
			
		||||
                                } catch (Throwable e)
 | 
			
		||||
                                {
 | 
			
		||||
                                    return false;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } else
 | 
			
		||||
                        {
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch (Throwable e)
 | 
			
		||||
                    {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                } else
 | 
			
		||||
                {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            } else
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final boolean doConnect()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized (createStreamLock)
 | 
			
		||||
        {
 | 
			
		||||
            if (hasBeenCancelled.get())
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    inputStream = getCancellableStream(true);
 | 
			
		||||
                } catch (ExecutionException e)
 | 
			
		||||
                {
 | 
			
		||||
                    if (connection.getResponseCode() < 400)
 | 
			
		||||
                    {
 | 
			
		||||
                        statusCode[0] = connection.getResponseCode();
 | 
			
		||||
                        connection.disconnect();
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                } finally
 | 
			
		||||
                {
 | 
			
		||||
                    statusCode[0] = connection.getResponseCode();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (statusCode[0] >= 400)
 | 
			
		||||
                        inputStream = getCancellableStream(false);
 | 
			
		||||
                    else
 | 
			
		||||
                        inputStream = getCancellableStream(true);
 | 
			
		||||
                } catch (ExecutionException e)
 | 
			
		||||
                {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
 | 
			
		||||
                {
 | 
			
		||||
                    if (entry.getKey() != null && entry.getValue() != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        responseHeaders.append(entry.getKey() + ": "
 | 
			
		||||
                                + android.text.TextUtils.join(",", entry.getValue()) + "\n");
 | 
			
		||||
 | 
			
		||||
                        if (entry.getKey().compareTo("Content-Length") == 0)
 | 
			
		||||
                            totalLength = Integer.decode(entry.getValue().get(0));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            } catch (IOException e)
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static class DisconnectionRunnable implements Runnable
 | 
			
		||||
    {
 | 
			
		||||
        public DisconnectionRunnable(HttpURLConnection theConnection,
 | 
			
		||||
                                     InputStream theInputStream,
 | 
			
		||||
                                     ReentrantLock theCreateStreamLock,
 | 
			
		||||
                                     Object theCreateFutureLock,
 | 
			
		||||
                                     Future<BufferedInputStream> theStreamFuture)
 | 
			
		||||
        {
 | 
			
		||||
            connectionToDisconnect = theConnection;
 | 
			
		||||
            inputStream = theInputStream;
 | 
			
		||||
            createStreamLock = theCreateStreamLock;
 | 
			
		||||
            createFutureLock = theCreateFutureLock;
 | 
			
		||||
            streamFuture = theStreamFuture;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void run()
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (!createStreamLock.tryLock())
 | 
			
		||||
                {
 | 
			
		||||
                    synchronized (createFutureLock)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (streamFuture != null)
 | 
			
		||||
                            streamFuture.cancel(true);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    createStreamLock.lock();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (connectionToDisconnect != null)
 | 
			
		||||
                    connectionToDisconnect.disconnect();
 | 
			
		||||
 | 
			
		||||
                if (inputStream != null)
 | 
			
		||||
                    inputStream.close();
 | 
			
		||||
            } catch (IOException e)
 | 
			
		||||
            {
 | 
			
		||||
            } finally
 | 
			
		||||
            {
 | 
			
		||||
                createStreamLock.unlock();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private HttpURLConnection connectionToDisconnect;
 | 
			
		||||
        private InputStream inputStream;
 | 
			
		||||
        private ReentrantLock createStreamLock;
 | 
			
		||||
        private Object createFutureLock;
 | 
			
		||||
        Future<BufferedInputStream> streamFuture;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final void release()
 | 
			
		||||
    {
 | 
			
		||||
        DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable(connection,
 | 
			
		||||
                inputStream,
 | 
			
		||||
                createStreamLock,
 | 
			
		||||
                createFutureLock,
 | 
			
		||||
                streamFuture);
 | 
			
		||||
 | 
			
		||||
        synchronized (createStreamLock)
 | 
			
		||||
        {
 | 
			
		||||
            hasBeenCancelled.set(true);
 | 
			
		||||
 | 
			
		||||
            connection = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Thread disconnectionThread = new Thread(disconnectionRunnable);
 | 
			
		||||
        disconnectionThread.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final int read(byte[] buffer, int numBytes)
 | 
			
		||||
    {
 | 
			
		||||
        int num = 0;
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            synchronized (createStreamLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (inputStream != null)
 | 
			
		||||
                    num = inputStream.read(buffer, 0, numBytes);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (IOException e)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (num > 0)
 | 
			
		||||
            position += num;
 | 
			
		||||
 | 
			
		||||
        return num;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final long getPosition()
 | 
			
		||||
    {
 | 
			
		||||
        return position;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final long getTotalLength()
 | 
			
		||||
    {
 | 
			
		||||
        return totalLength;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final boolean isExhausted()
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final boolean setPosition(long newPos)
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isPost;
 | 
			
		||||
    private byte[] postData;
 | 
			
		||||
    private String headers;
 | 
			
		||||
    private int timeOutMs;
 | 
			
		||||
    String httpRequestCmd;
 | 
			
		||||
    private HttpURLConnection connection;
 | 
			
		||||
    private int[] statusCode;
 | 
			
		||||
    private StringBuffer responseHeaders;
 | 
			
		||||
    private int totalLength;
 | 
			
		||||
    private int numRedirectsToFollow;
 | 
			
		||||
    private InputStream inputStream;
 | 
			
		||||
    private long position;
 | 
			
		||||
    private final ReentrantLock createStreamLock = new ReentrantLock();
 | 
			
		||||
    private final Object createFutureLock = new Object();
 | 
			
		||||
    private AtomicBoolean hasBeenCancelled = new AtomicBoolean();
 | 
			
		||||
 | 
			
		||||
    private final ExecutorService executor = Executors.newCachedThreadPool(Executors.defaultThreadFactory());
 | 
			
		||||
    Future<BufferedInputStream> streamFuture;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,15 @@
 | 
			
		||||
package com.roli.juce;
 | 
			
		||||
 | 
			
		||||
import com.roli.juce.Java;
 | 
			
		||||
 | 
			
		||||
import android.app.Application;
 | 
			
		||||
 | 
			
		||||
public class JuceApp extends Application
 | 
			
		||||
{
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreate ()
 | 
			
		||||
    {
 | 
			
		||||
        super.onCreate ();
 | 
			
		||||
        Java.initialiseJUCE (this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
package com.roli.juce;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
 | 
			
		||||
public class Java
 | 
			
		||||
{
 | 
			
		||||
    static
 | 
			
		||||
    {
 | 
			
		||||
        System.loadLibrary ("juce_jni");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public native static void initialiseJUCE (Context appContext);
 | 
			
		||||
}
 | 
			
		||||
@ -23,47 +23,97 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
  METHOD (constructor, "<init>",     "(Landroid/content/Context;Landroid/media/MediaScannerConnection$MediaScannerConnectionClient;)V") \
 | 
			
		||||
  METHOD (connect,     "connect",    "()V") \
 | 
			
		||||
  METHOD (disconnect,  "disconnect", "()V") \
 | 
			
		||||
  METHOD (scanFile,    "scanFile",   "(Ljava/lang/String;Ljava/lang/String;)V") \
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection");
 | 
			
		||||
DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (query,            "query",            "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;") \
 | 
			
		||||
 METHOD (openInputStream,  "openInputStream",  "(Landroid/net/Uri;)Ljava/io/InputStream;") \
 | 
			
		||||
 METHOD (openOutputStream, "openOutputStream", "(Landroid/net/Uri;)Ljava/io/OutputStream;")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver");
 | 
			
		||||
DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (moveToFirst,     "moveToFirst",     "()Z") \
 | 
			
		||||
 METHOD (getColumnIndex,  "getColumnIndex",  "(Ljava/lang/String;)I") \
 | 
			
		||||
 METHOD (getString,       "getString",       "(I)Ljava/lang/String;") \
 | 
			
		||||
 METHOD (close,           "close",           "()V") \
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidCursor, "android/database/Cursor");
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidCursor, "android/database/Cursor")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 STATICMETHOD (getExternalStorageDirectory, "getExternalStorageDirectory", "()Ljava/io/File;") \
 | 
			
		||||
 STATICMETHOD (getExternalStoragePublicDirectory, "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;") \
 | 
			
		||||
 STATICMETHOD (getDataDirectory, "getDataDirectory", "()Ljava/io/File;")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidEnvironment, "android/os/Environment");
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidEnvironment, "android/os/Environment")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (close, "close", "()V") \
 | 
			
		||||
 METHOD (flush, "flush", "()V") \
 | 
			
		||||
 METHOD (write, "write", "([BII)V")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidOutputStream, "java/io/OutputStream");
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidOutputStream, "java/io/OutputStream")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 FIELD (publicSourceDir, "publicSourceDir", "Ljava/lang/String;") \
 | 
			
		||||
 FIELD (dataDir, "dataDir", "Ljava/lang/String;")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidApplicationInfo, "android/content/pm/ApplicationInfo")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
static File juceFile (LocalRef<jobject> obj)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    if (env->IsInstanceOf (obj.get(), JavaFile) != 0)
 | 
			
		||||
        return File (juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (obj.get(),
 | 
			
		||||
                                                                                     JavaFile.getAbsolutePath))));
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static File getWellKnownFolder (const char* folderId)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
    auto fieldId = env->GetStaticFieldID (AndroidEnvironment, folderId, "Ljava/lang/String;");
 | 
			
		||||
 | 
			
		||||
    if (fieldId == 0)
 | 
			
		||||
    {
 | 
			
		||||
        // unknown field in environment
 | 
			
		||||
        jassertfalse;
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> fieldValue (env->GetStaticObjectField (AndroidEnvironment, fieldId));
 | 
			
		||||
 | 
			
		||||
    if (fieldValue == nullptr)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> downloadFolder (env->CallStaticObjectMethod (AndroidEnvironment,
 | 
			
		||||
                                                                   AndroidEnvironment.getExternalStoragePublicDirectory,
 | 
			
		||||
                                                                   fieldValue.get()));
 | 
			
		||||
 | 
			
		||||
    return (downloadFolder ? juceFile (downloadFolder) : File());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static LocalRef<jobject> urlToUri (const URL& url)
 | 
			
		||||
{
 | 
			
		||||
    return LocalRef<jobject> (getEnv()->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
 | 
			
		||||
                                                                javaString (url.toString (true)).get()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
struct AndroidContentUriResolver
 | 
			
		||||
{
 | 
			
		||||
@ -74,7 +124,7 @@ public:
 | 
			
		||||
        jassert (url.getScheme() == "content");
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        LocalRef<jobject> contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver));
 | 
			
		||||
        LocalRef<jobject> contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver));
 | 
			
		||||
 | 
			
		||||
        if (contentResolver)
 | 
			
		||||
            return LocalRef<jobject> ((env->CallObjectMethod (contentResolver.get(),
 | 
			
		||||
@ -116,7 +166,7 @@ public:
 | 
			
		||||
            else if (type.equalsIgnoreCase ("downloads"))
 | 
			
		||||
            {
 | 
			
		||||
                auto subDownloadPath = url.getSubPath().fromFirstOccurrenceOf ("tree/downloads", false, false);
 | 
			
		||||
                return File (getWellKnownFolder ("Download").getFullPathName() + "/" + subDownloadPath);
 | 
			
		||||
                return File (getWellKnownFolder ("DIRECTORY_DOWNLOADS").getFullPathName() + "/" + subDownloadPath);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
@ -142,7 +192,7 @@ public:
 | 
			
		||||
    {
 | 
			
		||||
        auto uri = urlToUri (url);
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
        LocalRef<jobject> contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver));
 | 
			
		||||
        LocalRef<jobject> contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver));
 | 
			
		||||
 | 
			
		||||
        if (contentResolver == 0)
 | 
			
		||||
            return {};
 | 
			
		||||
@ -166,7 +216,7 @@ private:
 | 
			
		||||
    {
 | 
			
		||||
        auto uri = urlToUri (url);
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
        LocalRef<jobject> contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver));
 | 
			
		||||
        LocalRef<jobject> contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver));
 | 
			
		||||
 | 
			
		||||
        if (contentResolver)
 | 
			
		||||
        {
 | 
			
		||||
@ -188,6 +238,13 @@ private:
 | 
			
		||||
                                                             uri.get(), projection.get(), jSelection.get(),
 | 
			
		||||
                                                             args.get(), nullptr));
 | 
			
		||||
 | 
			
		||||
            if (jniCheckHasExceptionOccurredAndClear())
 | 
			
		||||
            {
 | 
			
		||||
                // An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission?
 | 
			
		||||
                jassertfalse;
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (cursor)
 | 
			
		||||
            {
 | 
			
		||||
                if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0)
 | 
			
		||||
@ -210,17 +267,6 @@ private:
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static File getWellKnownFolder (const String& folderId)
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
        LocalRef<jobject> downloadFolder (env->CallStaticObjectMethod (AndroidEnvironment,
 | 
			
		||||
                                                                       AndroidEnvironment.getExternalStoragePublicDirectory,
 | 
			
		||||
                                                                       javaString (folderId).get()));
 | 
			
		||||
 | 
			
		||||
        return (downloadFolder ? juceFile (downloadFolder) : File());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static File getStorageDevicePath (const String& storageId)
 | 
			
		||||
    {
 | 
			
		||||
@ -247,15 +293,15 @@ private:
 | 
			
		||||
    {
 | 
			
		||||
        Array<File> results;
 | 
			
		||||
 | 
			
		||||
        if (getSDKVersion() >= 19)
 | 
			
		||||
        if (getAndroidSDKVersion() >= 19)
 | 
			
		||||
        {
 | 
			
		||||
            auto* env = getEnv();
 | 
			
		||||
            static jmethodID m = (env->GetMethodID (JuceAppActivity, "getExternalFilesDirs",
 | 
			
		||||
            static jmethodID m = (env->GetMethodID (AndroidContext, "getExternalFilesDirs",
 | 
			
		||||
                                                    "(Ljava/lang/String;)[Ljava/io/File;"));
 | 
			
		||||
            if (m == 0)
 | 
			
		||||
                return {};
 | 
			
		||||
 | 
			
		||||
            auto paths = convertFileArray (LocalRef<jobject> (android.activity.callObjectMethod (m, nullptr)));
 | 
			
		||||
            auto paths = convertFileArray (LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(), m, nullptr)));
 | 
			
		||||
 | 
			
		||||
            for (auto path : paths)
 | 
			
		||||
                results.add (getMountPointForFile (path));
 | 
			
		||||
@ -341,33 +387,6 @@ private:
 | 
			
		||||
        return files;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static File juceFile (LocalRef<jobject> obj)
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        if (env->IsInstanceOf (obj.get(), JavaFile) != 0)
 | 
			
		||||
            return File (juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (obj.get(),
 | 
			
		||||
                                                        JavaFile.getAbsolutePath))));
 | 
			
		||||
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static int getSDKVersion()
 | 
			
		||||
    {
 | 
			
		||||
        static int sdkVersion
 | 
			
		||||
            = getEnv()->CallStaticIntMethod (JuceAppActivity,
 | 
			
		||||
                                             JuceAppActivity.getAndroidSDKVersion);
 | 
			
		||||
 | 
			
		||||
        return sdkVersion;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static LocalRef<jobject> urlToUri (const URL& url)
 | 
			
		||||
    {
 | 
			
		||||
        return LocalRef<jobject> (getEnv()->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
 | 
			
		||||
                                                                    javaString (url.toString (true)).get()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    static String getStringUsingDataColumn (const String& columnNameToUse, JNIEnv* env,
 | 
			
		||||
                                            const LocalRef<jobject>& uri,
 | 
			
		||||
@ -380,6 +399,13 @@ private:
 | 
			
		||||
                                                         uri.get(), projection.get(), nullptr,
 | 
			
		||||
                                                         nullptr, nullptr));
 | 
			
		||||
 | 
			
		||||
        if (jniCheckHasExceptionOccurredAndClear())
 | 
			
		||||
        {
 | 
			
		||||
            // An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission?
 | 
			
		||||
            jassertfalse;
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (cursor == 0)
 | 
			
		||||
            return {};
 | 
			
		||||
 | 
			
		||||
@ -458,7 +484,7 @@ OutputStream* juce_CreateContentURIOutputStream (const URL& url)
 | 
			
		||||
{
 | 
			
		||||
    auto stream = AndroidContentUriResolver::getStreamForContentUri (url, false);
 | 
			
		||||
 | 
			
		||||
    return (stream.get() != 0 ? new AndroidContentUriOutputStream (static_cast<LocalRef<jobject>&&> (stream)) : nullptr);
 | 
			
		||||
    return (stream.get() != 0 ? new AndroidContentUriOutputStream (std::move (stream)) : nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
@ -511,9 +537,24 @@ String File::getVersion() const
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static File getSpecialFile (jmethodID type)
 | 
			
		||||
static File getDocumentsDirectory()
 | 
			
		||||
{
 | 
			
		||||
    return File (juceString (LocalRef<jstring> ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity, type))));
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    if (getAndroidSDKVersion() >= 19)
 | 
			
		||||
        return getWellKnownFolder ("DIRECTORY_DOCUMENTS");
 | 
			
		||||
 | 
			
		||||
    return juceFile (LocalRef<jobject> (env->CallStaticObjectMethod (AndroidEnvironment, AndroidEnvironment.getDataDirectory)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static File getAppDataDir (bool dataDir)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> applicationInfo (env->CallObjectMethod (getAppContext().get(), AndroidContext.getApplicationInfo));
 | 
			
		||||
    LocalRef<jobject> jString (env->GetObjectField (applicationInfo.get(), dataDir ? AndroidApplicationInfo.dataDir : AndroidApplicationInfo.publicSourceDir));
 | 
			
		||||
 | 
			
		||||
    return  {juceString ((jstring) jString.get())};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
File File::getSpecialLocation (const SpecialLocationType type)
 | 
			
		||||
@ -524,20 +565,41 @@ File File::getSpecialLocation (const SpecialLocationType type)
 | 
			
		||||
        case userApplicationDataDirectory:
 | 
			
		||||
        case userDesktopDirectory:
 | 
			
		||||
        case commonApplicationDataDirectory:
 | 
			
		||||
            return File (android.appDataDir);
 | 
			
		||||
        {
 | 
			
		||||
            static File appDataDir = getAppDataDir (true);
 | 
			
		||||
            return appDataDir;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case userDocumentsDirectory:
 | 
			
		||||
        case commonDocumentsDirectory:  return getSpecialFile (JuceAppActivity.getDocumentsFolder);
 | 
			
		||||
        case userPicturesDirectory:     return getSpecialFile (JuceAppActivity.getPicturesFolder);
 | 
			
		||||
        case userMusicDirectory:        return getSpecialFile (JuceAppActivity.getMusicFolder);
 | 
			
		||||
        case userMoviesDirectory:       return getSpecialFile (JuceAppActivity.getMoviesFolder);
 | 
			
		||||
        case commonDocumentsDirectory:
 | 
			
		||||
        {
 | 
			
		||||
            static auto docsDir = getDocumentsDirectory();
 | 
			
		||||
            return docsDir;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case userPicturesDirectory:
 | 
			
		||||
        {
 | 
			
		||||
            static auto picturesDir = getWellKnownFolder ("DIRECTORY_PICTURES");
 | 
			
		||||
            return picturesDir;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case userMusicDirectory:
 | 
			
		||||
        {
 | 
			
		||||
            static auto musicDir = getWellKnownFolder ("DIRECTORY_MUSIC");
 | 
			
		||||
            return musicDir;
 | 
			
		||||
        }
 | 
			
		||||
        case userMoviesDirectory:
 | 
			
		||||
        {
 | 
			
		||||
            static auto moviesDir = getWellKnownFolder ("DIRECTORY_MOVIES");
 | 
			
		||||
            return moviesDir;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case globalApplicationsDirectory:
 | 
			
		||||
            return File ("/system/app");
 | 
			
		||||
 | 
			
		||||
        case tempDirectory:
 | 
			
		||||
        {
 | 
			
		||||
            File tmp = File (android.appDataDir).getChildFile (".temp");
 | 
			
		||||
            File tmp = getSpecialLocation (commonApplicationDataDirectory).getChildFile (".temp");
 | 
			
		||||
            tmp.createDirectory();
 | 
			
		||||
            return File (tmp.getFullPathName());
 | 
			
		||||
        }
 | 
			
		||||
@ -546,7 +608,7 @@ File File::getSpecialLocation (const SpecialLocationType type)
 | 
			
		||||
        case currentExecutableFile:
 | 
			
		||||
        case currentApplicationFile:
 | 
			
		||||
        case hostApplicationPath:
 | 
			
		||||
            return juce_getExecutableFile();
 | 
			
		||||
            return getAppDataDir (false);
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            jassertfalse; // unknown type?
 | 
			
		||||
@ -567,8 +629,13 @@ bool File::moveToTrash() const
 | 
			
		||||
 | 
			
		||||
JUCE_API bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String&)
 | 
			
		||||
{
 | 
			
		||||
    const LocalRef<jstring> t (javaString (fileName));
 | 
			
		||||
    android.activity.callVoidMethod (JuceAppActivity.launchURL, t.get());
 | 
			
		||||
    URL targetURL (fileName);
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    const LocalRef<jstring> action (javaString ("android.intent.action.VIEW"));
 | 
			
		||||
    LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructWithUri, action.get(), urlToUri (targetURL).get()));
 | 
			
		||||
 | 
			
		||||
    env->CallVoidMethod (getCurrentActivity(), AndroidContext.startActivity, intent.get());
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -581,10 +648,10 @@ class SingleMediaScanner : public MediaScannerConnectionClient
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    SingleMediaScanner (const String& filename)
 | 
			
		||||
        : msc (getEnv()->NewObject (MediaScannerConnection,
 | 
			
		||||
                                    MediaScannerConnection.constructor,
 | 
			
		||||
                                    android.activity.get(),
 | 
			
		||||
                                    CreateJavaInterface (this, "android/media/MediaScannerConnection$MediaScannerConnectionClient").get())),
 | 
			
		||||
        : msc (LocalRef<jobject> (getEnv()->NewObject (MediaScannerConnection,
 | 
			
		||||
                                                       MediaScannerConnection.constructor,
 | 
			
		||||
                                                       getAppContext().get(),
 | 
			
		||||
                                                       CreateJavaInterface (this, "android/media/MediaScannerConnection$MediaScannerConnectionClient").get()))),
 | 
			
		||||
          file (filename)
 | 
			
		||||
    {
 | 
			
		||||
        getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.connect);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										687
									
								
								modules/juce_core/native/juce_android_JNIHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										687
									
								
								modules/juce_core/native/juce_android_JNIHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,687 @@
 | 
			
		||||
/*
 | 
			
		||||
 ==============================================================================
 | 
			
		||||
 | 
			
		||||
 This file is part of the JUCE library.
 | 
			
		||||
 Copyright (c) 2017 - ROLI Ltd.
 | 
			
		||||
 | 
			
		||||
 JUCE is an open source library subject to commercial or open-source
 | 
			
		||||
 licensing.
 | 
			
		||||
 | 
			
		||||
 The code included in this file is provided under the terms of the ISC license
 | 
			
		||||
 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
 | 
			
		||||
 To use, copy, modify, and/or distribute this software for any purpose with or
 | 
			
		||||
 without fee is hereby granted provided that the above copyright notice and
 | 
			
		||||
 this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 | 
			
		||||
 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 | 
			
		||||
 DISCLAIMED.
 | 
			
		||||
 | 
			
		||||
 ==============================================================================
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
static const uint8 invocationHandleByteCode[] = {
 | 
			
		||||
31,139,8,8,170,94,229,91,0,3,105,110,118,111,99,97,116,105,111,110,95,104,97,110,100,108,101,114,46,100,101,120,0,109,
 | 
			
		||||
148,75,107,19,81,20,199,207,189,119,38,169,181,109,98,218,138,72,23,81,20,68,193,169,218,130,144,42,130,162,53,140,15,
 | 
			
		||||
104,200,66,187,232,52,185,181,147,78,103,98,58,141,33,184,168,197,47,224,66,168,187,186,241,83,168,184,16,247,162,31,193,
 | 
			
		||||
69,151,174,220,40,234,255,62,154,4,237,192,239,62,206,57,115,30,115,239,153,186,236,12,79,95,154,165,159,187,91,95,227,
 | 
			
		||||
217,197,247,87,42,31,231,191,156,157,58,241,253,199,220,201,79,75,175,118,58,14,81,147,136,58,213,153,2,217,231,131,32,
 | 
			
		||||
154,32,35,63,164,246,0,102,244,13,48,48,129,33,139,121,138,153,125,5,131,131,119,82,204,203,156,168,1,214,65,10,186,224,
 | 
			
		||||
53,120,7,62,131,61,144,131,237,57,112,30,92,4,183,192,93,176,4,154,160,11,182,193,11,97,252,171,216,46,200,144,137,59,
 | 
			
		||||
100,243,26,6,35,0,46,9,166,116,131,155,89,113,159,27,125,214,214,116,216,174,23,185,241,89,208,181,8,173,99,240,48,106,
 | 
			
		||||
247,99,182,198,156,149,231,245,204,232,136,246,203,173,189,65,189,61,7,209,31,60,167,48,239,26,119,90,183,35,84,190,66,
 | 
			
		||||
175,201,230,218,204,43,201,81,172,30,76,131,25,66,180,190,133,169,81,107,139,164,243,112,123,25,154,253,194,53,232,17,
 | 
			
		||||
231,2,74,190,140,106,212,190,57,205,201,97,99,168,207,209,223,71,61,147,202,182,57,104,59,74,11,45,212,255,56,251,60,251,
 | 
			
		||||
50,251,166,157,81,81,71,80,83,129,206,252,238,133,239,101,162,242,72,237,217,186,246,251,171,106,51,248,242,162,183,218,
 | 
			
		||||
183,207,204,133,113,152,94,37,86,38,215,47,251,190,79,142,175,198,211,126,45,89,247,90,73,20,122,141,205,154,244,202,24,
 | 
			
		||||
110,199,237,164,22,164,97,18,207,7,113,61,146,173,18,29,247,235,65,212,14,215,188,32,142,147,84,235,188,202,106,43,121,
 | 
			
		||||
178,81,162,130,223,8,218,129,23,5,241,35,239,222,114,67,214,210,18,77,14,200,180,93,176,28,201,18,162,245,197,45,185,18,
 | 
			
		||||
193,214,59,48,218,255,102,119,100,186,154,212,75,196,170,196,171,101,26,127,120,64,84,183,22,201,160,69,249,122,184,209,
 | 
			
		||||
12,210,218,234,205,48,14,162,176,43,105,108,95,162,130,173,73,26,90,217,215,100,66,35,25,141,145,66,91,94,79,226,84,118,
 | 
			
		||||
82,114,219,65,180,41,137,115,54,62,197,142,57,196,132,186,86,207,182,156,95,156,111,115,98,10,182,43,4,123,43,24,219,19,
 | 
			
		||||
185,127,206,70,247,159,237,77,62,208,159,98,160,71,157,129,62,117,169,223,171,25,234,247,171,200,155,181,62,231,162,121,
 | 
			
		||||
231,169,178,41,26,185,186,207,44,111,228,234,142,243,162,137,171,250,219,41,246,239,56,217,181,190,251,214,167,250,127,
 | 
			
		||||
252,5,76,239,47,69,120,4,0,0};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 STATICMETHOD (newProxyInstance, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;") \
 | 
			
		||||
 | 
			
		||||
 DECLARE_JNI_CLASS (JavaProxy, "java/lang/reflect/Proxy")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (constructor, "<init>", "(J)V") \
 | 
			
		||||
 METHOD (clear, "clear", "()V") \
 | 
			
		||||
 CALLBACK (juce_invokeImplementer, "dispatchInvoke", "(JLjava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;") \
 | 
			
		||||
 CALLBACK (juce_dispatchDelete, "dispatchFinalize", "(J)V")
 | 
			
		||||
 | 
			
		||||
 DECLARE_JNI_CLASS_WITH_BYTECODE (JuceInvocationHandler, "com/roli/juce/JuceInvocationHandler", 10, invocationHandleByteCode, sizeof (invocationHandleByteCode))
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD       (findClass,            "findClass",            "(Ljava/lang/String;)Ljava/lang/Class;") \
 | 
			
		||||
 STATICMETHOD (getSystemClassLoader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (JavaClassLoader, "java/lang/ClassLoader")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidDexClassLoader, "dalvik/system/DexClassLoader")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (constructor, "<init>", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidInMemoryDexClassLoader, "dalvik/system/InMemoryDexClassLoader", 26)
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
struct SystemJavaClassComparator
 | 
			
		||||
{
 | 
			
		||||
    static int compareElements (JNIClassBase* first, JNIClassBase* second)
 | 
			
		||||
    {
 | 
			
		||||
        auto isSysClassA = isSystemClass (first);
 | 
			
		||||
        auto isSysClassB = isSystemClass (second);
 | 
			
		||||
 | 
			
		||||
        if ((! isSysClassA) && (! isSysClassB))
 | 
			
		||||
        {
 | 
			
		||||
            return DefaultElementComparator<bool>::compareElements (first != nullptr  ? first->byteCode  != nullptr : false,
 | 
			
		||||
                                                                    second != nullptr ? second->byteCode != nullptr : false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return DefaultElementComparator<bool>::compareElements (isSystemClass (first),
 | 
			
		||||
                                                                isSystemClass (second));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static bool isSystemClass (JNIClassBase* cls)
 | 
			
		||||
    {
 | 
			
		||||
        if (cls == nullptr)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        String path (cls->getClassPath());
 | 
			
		||||
 | 
			
		||||
        return path.startsWith ("java/")
 | 
			
		||||
            || path.startsWith ("android/")
 | 
			
		||||
            || path.startsWith ("dalvik/");
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
JNIClassBase::JNIClassBase (const char* cp, int classMinSDK, const void* bc, size_t n)
 | 
			
		||||
    : classPath (cp), byteCode (bc), byteCodeSize (n), minSDK (classMinSDK), classRef (0)
 | 
			
		||||
{
 | 
			
		||||
    SystemJavaClassComparator comparator;
 | 
			
		||||
 | 
			
		||||
    getClasses().addSorted (comparator, this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
JNIClassBase::~JNIClassBase()
 | 
			
		||||
{
 | 
			
		||||
    getClasses().removeFirstMatchingValue (this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Array<JNIClassBase*>& JNIClassBase::getClasses()
 | 
			
		||||
{
 | 
			
		||||
    static Array<JNIClassBase*> classes;
 | 
			
		||||
    return classes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get code cache directory without yet having a context object
 | 
			
		||||
static File getCodeCacheDirectory()
 | 
			
		||||
{
 | 
			
		||||
    int pid = getpid();
 | 
			
		||||
    File cmdline("/proc/" + String(pid) + "/cmdline");
 | 
			
		||||
 | 
			
		||||
    auto bundleId = cmdline.loadFileAsString().trimStart().trimEnd();
 | 
			
		||||
 | 
			
		||||
    if (bundleId.isEmpty())
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    return File("/data/data/" + bundleId + "/code_cache");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::initialise (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    auto sdkVersion = getAndroidSDKVersion();
 | 
			
		||||
 | 
			
		||||
    if (sdkVersion >= minSDK)
 | 
			
		||||
    {
 | 
			
		||||
        LocalRef<jstring> classNameAndPackage (javaString (String (classPath).replaceCharacter (L'/', L'.')));
 | 
			
		||||
        static Array<GlobalRef> byteCodeLoaders;
 | 
			
		||||
 | 
			
		||||
        if (! SystemJavaClassComparator::isSystemClass(this))
 | 
			
		||||
        {
 | 
			
		||||
            LocalRef<jobject> defaultClassLoader (env->CallStaticObjectMethod (JavaClassLoader, JavaClassLoader.getSystemClassLoader));
 | 
			
		||||
            tryLoadingClassWithClassLoader (env, defaultClassLoader.get());
 | 
			
		||||
 | 
			
		||||
            if (classRef == 0)
 | 
			
		||||
            {
 | 
			
		||||
                for (auto& byteCodeLoader : byteCodeLoaders)
 | 
			
		||||
                {
 | 
			
		||||
                    tryLoadingClassWithClassLoader (env, byteCodeLoader.get());
 | 
			
		||||
 | 
			
		||||
                    if (classRef != 0)
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // fallback by trying to load the class from bytecode
 | 
			
		||||
                if (byteCode != nullptr)
 | 
			
		||||
                {
 | 
			
		||||
                    LocalRef<jobject> byteCodeClassLoader;
 | 
			
		||||
 | 
			
		||||
                    MemoryOutputStream uncompressedByteCode;
 | 
			
		||||
 | 
			
		||||
                    {
 | 
			
		||||
                        MemoryInputStream rawGZipData (byteCode, byteCodeSize, false);
 | 
			
		||||
                        GZIPDecompressorInputStream gzipStream (&rawGZipData, false, GZIPDecompressorInputStream::gzipFormat);
 | 
			
		||||
                        uncompressedByteCode.writeFromInputStream (gzipStream, -1);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (sdkVersion >= 26)
 | 
			
		||||
                    {
 | 
			
		||||
                        LocalRef<jbyteArray> byteArray (env->NewByteArray ((jsize) uncompressedByteCode.getDataSize()));
 | 
			
		||||
                        jboolean isCopy;
 | 
			
		||||
                        auto* dst = env->GetByteArrayElements (byteArray.get(), &isCopy);
 | 
			
		||||
                        memcpy (dst, uncompressedByteCode.getData(), uncompressedByteCode.getDataSize());
 | 
			
		||||
                        env->ReleaseByteArrayElements (byteArray.get(), dst, 0);
 | 
			
		||||
 | 
			
		||||
                        LocalRef<jobject> byteBuffer (env->CallStaticObjectMethod (JavaByteBuffer, JavaByteBuffer.wrap, byteArray.get()));
 | 
			
		||||
 | 
			
		||||
                        byteCodeClassLoader = LocalRef<jobject> (env->NewObject (AndroidInMemoryDexClassLoader,
 | 
			
		||||
                                                                                 AndroidInMemoryDexClassLoader.constructor,
 | 
			
		||||
                                                                                 byteBuffer.get(), defaultClassLoader.get()));
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (uncompressedByteCode.getDataSize() >= 32)
 | 
			
		||||
                    {
 | 
			
		||||
                        auto codeCacheDir = getCodeCacheDirectory();
 | 
			
		||||
 | 
			
		||||
                        // The dex file has an embedded 20-byte long SHA-1 signature at offset 12
 | 
			
		||||
                        auto fileName = String::toHexString ((char*)uncompressedByteCode.getData() + 12, 20, 0) + ".dex";
 | 
			
		||||
                        auto dexFile = codeCacheDir.getChildFile (fileName);
 | 
			
		||||
                        auto optimizedDirectory = codeCacheDir.getChildFile ("optimized_cache");
 | 
			
		||||
                        optimizedDirectory.createDirectory();
 | 
			
		||||
 | 
			
		||||
                        if (dexFile.replaceWithData (uncompressedByteCode.getData(), uncompressedByteCode.getDataSize()))
 | 
			
		||||
                        {
 | 
			
		||||
                            byteCodeClassLoader = LocalRef<jobject> (env->NewObject (AndroidDexClassLoader,
 | 
			
		||||
                                                                                     AndroidDexClassLoader.constructor,
 | 
			
		||||
                                                                                     javaString (dexFile.getFullPathName()).get(),
 | 
			
		||||
                                                                                     javaString (optimizedDirectory.getFullPathName()).get(),
 | 
			
		||||
                                                                                     nullptr,
 | 
			
		||||
                                                                                     defaultClassLoader.get()));
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            // can't write to cache folder
 | 
			
		||||
                            jassertfalse;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (byteCodeClassLoader != nullptr)
 | 
			
		||||
                    {
 | 
			
		||||
                        tryLoadingClassWithClassLoader (env, byteCodeClassLoader.get());
 | 
			
		||||
                        byteCodeLoaders.add (GlobalRef(byteCodeClassLoader));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (classRef == 0)
 | 
			
		||||
            classRef = (jclass) env->NewGlobalRef (LocalRef<jobject> (env->FindClass (classPath)));
 | 
			
		||||
 | 
			
		||||
        jassert (classRef != 0);
 | 
			
		||||
        initialiseFields (env);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::tryLoadingClassWithClassLoader (JNIEnv* env, jobject classLoader)
 | 
			
		||||
{
 | 
			
		||||
    LocalRef<jstring> classNameAndPackage (javaString (String (classPath).replaceCharacter (L'/', L'.')));
 | 
			
		||||
 | 
			
		||||
    // Android SDK <= 19 has a bug where the class loader might throw an exception but still return
 | 
			
		||||
    // a non-nullptr. So don't assign the result of this call to a jobject just yet...
 | 
			
		||||
    auto classObj = env->CallObjectMethod (classLoader, JavaClassLoader.findClass, classNameAndPackage.get());
 | 
			
		||||
 | 
			
		||||
    if (jthrowable exception = env->ExceptionOccurred ())
 | 
			
		||||
    {
 | 
			
		||||
        env->ExceptionClear();
 | 
			
		||||
        classObj = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // later versions of Android don't throw at all, so re-check the object
 | 
			
		||||
    if (classObj != nullptr)
 | 
			
		||||
        classRef = (jclass) env->NewGlobalRef (LocalRef<jobject> (classObj));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::release (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    if (classRef != 0)
 | 
			
		||||
        env->DeleteGlobalRef (classRef);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::initialiseAllClasses (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    const Array<JNIClassBase*>& classes = getClasses();
 | 
			
		||||
    for (int i = classes.size(); --i >= 0;)
 | 
			
		||||
        classes.getUnchecked(i)->initialise (env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::releaseAllClasses (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    const Array<JNIClassBase*>& classes = getClasses();
 | 
			
		||||
    for (int i = classes.size(); --i >= 0;)
 | 
			
		||||
        classes.getUnchecked(i)->release (env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jmethodID JNIClassBase::resolveMethod (JNIEnv* env, const char* methodName, const char* params)
 | 
			
		||||
{
 | 
			
		||||
    jmethodID m = env->GetMethodID (classRef, methodName, params);
 | 
			
		||||
    jassert (m != 0);
 | 
			
		||||
    return m;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params)
 | 
			
		||||
{
 | 
			
		||||
    jmethodID m = env->GetStaticMethodID (classRef, methodName, params);
 | 
			
		||||
    jassert (m != 0);
 | 
			
		||||
    return m;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature)
 | 
			
		||||
{
 | 
			
		||||
    jfieldID f = env->GetFieldID (classRef, fieldName, signature);
 | 
			
		||||
    jassert (f != 0);
 | 
			
		||||
    return f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature)
 | 
			
		||||
{
 | 
			
		||||
    jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature);
 | 
			
		||||
    jassert (f != 0);
 | 
			
		||||
    return f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::resolveCallbacks (JNIEnv* env, const Array<JNINativeMethod>& nativeCallbacks)
 | 
			
		||||
{
 | 
			
		||||
    if (nativeCallbacks.size() > 0)
 | 
			
		||||
        env->RegisterNatives (classRef, nativeCallbacks.begin(), (jint) nativeCallbacks.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
 | 
			
		||||
                                       const StringArray& interfaceNames,
 | 
			
		||||
                                       LocalRef<jobject> subclass)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    implementer->javaSubClass = GlobalRef (subclass);
 | 
			
		||||
 | 
			
		||||
    // you need to override at least one interface
 | 
			
		||||
    jassert (interfaceNames.size() > 0);
 | 
			
		||||
 | 
			
		||||
    auto classArray = LocalRef<jobject> (env->NewObjectArray (interfaceNames.size(), JavaClass, nullptr));
 | 
			
		||||
    LocalRef<jobject> classLoader;
 | 
			
		||||
 | 
			
		||||
    for (auto i = 0; i < interfaceNames.size(); ++i)
 | 
			
		||||
    {
 | 
			
		||||
        auto aClass = LocalRef<jobject> (env->FindClass (interfaceNames[i].toRawUTF8()));
 | 
			
		||||
 | 
			
		||||
        if (aClass != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            if (i == 0)
 | 
			
		||||
                classLoader = LocalRef<jobject> (env->CallObjectMethod (aClass, JavaClass.getClassLoader));
 | 
			
		||||
 | 
			
		||||
            env->SetObjectArrayElement ((jobjectArray) classArray.get(), i, aClass);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            // interface class not found
 | 
			
		||||
            jassertfalse;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto invocationHandler = LocalRef<jobject> (env->NewObject (JuceInvocationHandler, JuceInvocationHandler.constructor,
 | 
			
		||||
                                                                reinterpret_cast<jlong> (implementer)));
 | 
			
		||||
 | 
			
		||||
    // CreateJavaInterface() is expected to be called just once for a given implementer
 | 
			
		||||
    jassert (implementer->invocationHandler == nullptr);
 | 
			
		||||
 | 
			
		||||
    implementer->invocationHandler = GlobalRef (invocationHandler);
 | 
			
		||||
 | 
			
		||||
    return LocalRef<jobject> (env->CallStaticObjectMethod (JavaProxy, JavaProxy.newProxyInstance,
 | 
			
		||||
                                                           classLoader.get(), classArray.get(),
 | 
			
		||||
                                                           invocationHandler.get()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
 | 
			
		||||
                                       const StringArray& interfaceNames)
 | 
			
		||||
{
 | 
			
		||||
    return CreateJavaInterface (implementer, interfaceNames,
 | 
			
		||||
                                LocalRef<jobject> (getEnv()->NewObject (JavaObject,
 | 
			
		||||
                                                                        JavaObject.constructor)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
 | 
			
		||||
                                       const String& interfaceName)
 | 
			
		||||
{
 | 
			
		||||
    return CreateJavaInterface (implementer, StringArray (interfaceName));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AndroidInterfaceImplementer::~AndroidInterfaceImplementer()
 | 
			
		||||
{
 | 
			
		||||
    clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AndroidInterfaceImplementer::clear()
 | 
			
		||||
{
 | 
			
		||||
    if (invocationHandler != nullptr)
 | 
			
		||||
        getEnv()->CallVoidMethod (invocationHandler,
 | 
			
		||||
                                  JuceInvocationHandler.clear);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jobject AndroidInterfaceImplementer::invoke (jobject /*proxy*/, jobject method, jobjectArray args)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
    return env->CallObjectMethod (method, JavaMethod.invoke, javaSubClass.get(), args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jobject juce_invokeImplementer (JNIEnv*, jobject /*object*/, jlong host, jobject proxy,
 | 
			
		||||
                                jobject method, jobjectArray args)
 | 
			
		||||
{
 | 
			
		||||
    if (auto* myself = reinterpret_cast<AndroidInterfaceImplementer*> (host))
 | 
			
		||||
        return myself->invoke (proxy, method, args);
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void juce_dispatchDelete (JNIEnv*, jobject /*object*/, jlong host)
 | 
			
		||||
{
 | 
			
		||||
    if (auto* myself = reinterpret_cast<AndroidInterfaceImplementer*> (host))
 | 
			
		||||
        delete myself;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
jobject ActivityLifecycleCallbacks::invoke (jobject proxy, jobject method, jobjectArray args)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
 | 
			
		||||
 | 
			
		||||
    auto activity = env->GetArrayLength (args) > 0 ? env->GetObjectArrayElement (args, 0) : (jobject) nullptr;
 | 
			
		||||
    auto bundle   = env->GetArrayLength (args) > 1 ? env->GetObjectArrayElement (args, 1) : (jobject) nullptr;
 | 
			
		||||
 | 
			
		||||
    if      (methodName == "onActivityCreated")             { onActivityCreated (activity, bundle); return nullptr; }
 | 
			
		||||
    else if (methodName == "onActivityDestroyed")           { onActivityDestroyed (activity); return nullptr; }
 | 
			
		||||
    else if (methodName == "onActivityPaused")              { onActivityPaused (activity); return nullptr; }
 | 
			
		||||
    else if (methodName == "onActivityResumed")             { onActivityResumed (activity); return nullptr; }
 | 
			
		||||
    else if (methodName == "onActivitySaveInstanceState")   { onActivitySaveInstanceState (activity, bundle); return nullptr; }
 | 
			
		||||
    else if (methodName == "onActivityStarted")             { onActivityStarted (activity); return nullptr; }
 | 
			
		||||
    else if (methodName == "onActivityStopped")             { onActivityStopped (activity); return nullptr; }
 | 
			
		||||
 | 
			
		||||
    return AndroidInterfaceImplementer::invoke (proxy, method, args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
int getAndroidSDKVersion()
 | 
			
		||||
{
 | 
			
		||||
    // this is used so often that we need to cache this
 | 
			
		||||
    static int sdkVersion = []
 | 
			
		||||
    {
 | 
			
		||||
        // don't use any jni helpers as they might not have been initialised yet
 | 
			
		||||
        // when this method is used
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        auto buildVersion = env->FindClass ("android/os/Build$VERSION");
 | 
			
		||||
        jassert (buildVersion != 0);
 | 
			
		||||
 | 
			
		||||
        auto sdkVersionField = env->GetStaticFieldID (buildVersion, "SDK_INT", "I");
 | 
			
		||||
        jassert (sdkVersionField != 0);
 | 
			
		||||
 | 
			
		||||
        return env->GetStaticIntField (buildVersion, sdkVersionField);
 | 
			
		||||
    }();
 | 
			
		||||
 | 
			
		||||
    return sdkVersion;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool isPermissionDeclaredInManifest (const String& requestedPermission)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> pkgManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager));
 | 
			
		||||
    LocalRef<jobject> pkgName (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName));
 | 
			
		||||
    LocalRef<jobject> pkgInfo (env->CallObjectMethod (pkgManager.get(), AndroidPackageManager.getPackageInfo,
 | 
			
		||||
                                                      pkgName.get(), 0x00001000 /* PERMISSIONS */));
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobjectArray> permissions ((jobjectArray) env->GetObjectField (pkgInfo.get(), AndroidPackageInfo.requestedPermissions));
 | 
			
		||||
    int n = env->GetArrayLength (permissions);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < n; ++i)
 | 
			
		||||
    {
 | 
			
		||||
        LocalRef<jstring> jstr ((jstring) env->GetObjectArrayElement (permissions, i));
 | 
			
		||||
        String permissionId (juceString (jstr));
 | 
			
		||||
 | 
			
		||||
        if (permissionId == requestedPermission)
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This byte-code is generated from native/java/com/roli/juce/FragmentOverlay.java with min sdk version 16
 | 
			
		||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
 | 
			
		||||
static const uint8 javaFragmentOverlay[] =
 | 
			
		||||
{31,139,8,8,197,105,229,91,0,3,70,114,97,103,109,101,110,116,79,118,101,114,108,97,121,46,100,101,120,0,133,149,93,136,
 | 
			
		||||
28,69,16,199,171,231,243,62,39,235,93,60,206,243,244,214,136,73,4,201,156,104,32,186,107,184,36,34,236,58,248,145,11,251,
 | 
			
		||||
112,241,101,216,29,215,137,123,51,155,153,217,35,1,65,61,2,201,131,8,10,126,97,2,17,84,16,204,91,30,228,240,73,130,95,40,
 | 
			
		||||
104,124,9,137,47,9,248,230,23,8,65,52,130,255,234,238,201,133,35,226,178,191,169,234,234,238,170,234,154,158,238,78,116,
 | 
			
		||||
100,100,254,129,157,116,170,242,197,47,171,219,126,253,250,218,79,107,167,30,234,189,251,229,123,127,239,158,219,187,86,
 | 
			
		||||
124,127,193,33,234,19,209,145,214,131,19,164,127,187,96,187,151,148,125,4,108,22,74,214,33,241,167,179,120,84,32,63,213,
 | 
			
		||||
237,186,65,244,130,69,244,12,228,121,147,232,34,248,29,252,1,174,130,191,192,63,224,118,140,217,9,154,224,121,240,34,88,
 | 
			
		||||
5,39,192,171,224,117,240,14,56,13,62,0,31,129,51,224,51,240,21,56,15,46,128,203,224,55,240,39,112,108,162,105,48,15,30,6,
 | 
			
		||||
77,240,44,56,1,94,3,167,193,25,176,6,62,7,223,2,164,73,72,135,176,76,114,193,16,24,214,107,29,5,147,188,102,0,247,114,
 | 
			
		||||
125,199,48,216,214,109,210,99,92,173,143,105,253,21,140,25,215,250,219,208,61,173,191,15,125,147,214,63,54,85,221,88,255,
 | 
			
		||||
4,250,45,90,63,7,125,66,235,223,200,88,130,166,136,243,52,100,12,3,217,221,169,219,91,116,30,51,196,227,84,63,203,91,181,
 | 
			
		||||
156,38,53,255,54,41,77,154,149,210,161,59,164,84,126,108,172,120,78,74,139,170,82,186,116,151,158,191,69,74,155,238,38,
 | 
			
		||||
181,102,65,164,163,40,157,127,67,142,146,38,44,108,251,193,86,53,236,87,120,44,103,190,84,229,10,148,253,151,116,127,217,
 | 
			
		||||
147,84,28,140,243,80,71,75,190,131,43,182,90,255,34,54,220,20,130,221,15,55,187,208,187,152,161,38,135,197,49,241,134,
 | 
			
		||||
251,225,138,51,12,95,30,241,76,94,255,207,152,195,107,74,171,130,14,192,163,11,235,24,205,136,41,74,170,38,170,60,74,75,
 | 
			
		||||
11,240,184,112,163,71,87,182,251,11,136,251,180,39,223,163,138,127,245,127,226,187,50,254,184,140,207,181,229,189,195,19,
 | 
			
		||||
249,253,165,21,206,231,166,113,230,55,145,37,60,93,55,71,239,57,210,82,233,195,178,46,66,83,238,55,238,45,117,30,97,72,
 | 
			
		||||
221,210,99,156,122,156,196,197,110,218,252,88,22,118,151,163,164,120,114,37,202,122,225,209,29,135,194,149,144,68,131,68,
 | 
			
		||||
147,140,102,64,34,160,217,32,76,58,89,26,119,252,176,223,247,31,141,195,94,218,45,103,213,104,250,122,111,59,77,10,152,
 | 
			
		||||
252,134,20,53,154,188,222,147,230,254,222,65,210,233,69,53,154,11,218,233,178,159,165,189,216,63,52,104,71,254,134,240,
 | 
			
		||||
53,154,8,56,3,191,23,38,93,127,177,200,226,164,91,35,209,34,171,213,104,4,252,12,2,50,90,77,178,91,77,54,176,128,197,108,
 | 
			
		||||
53,217,12,14,54,104,242,224,77,92,216,237,94,154,71,228,182,251,253,3,207,197,57,89,157,176,8,201,237,196,249,114,156,
 | 
			
		||||
231,52,214,141,138,61,89,119,192,169,228,228,162,21,164,73,23,230,44,76,138,253,81,62,232,193,92,73,147,61,237,34,94,137,
 | 
			
		||||
139,163,202,68,83,27,45,79,132,104,69,52,148,38,251,178,40,44,34,242,74,77,247,204,164,201,254,232,240,32,202,139,167,
 | 
			
		||||
162,140,67,199,105,146,107,111,213,255,238,211,179,221,52,89,44,194,172,160,113,173,104,251,104,127,125,2,141,102,202,
 | 
			
		||||
201,190,180,19,209,72,38,231,75,221,206,11,78,201,42,184,0,46,185,158,113,95,141,118,64,62,94,167,237,230,214,109,211,
 | 
			
		||||
174,119,252,77,26,19,219,93,175,126,238,248,18,85,205,173,247,204,194,246,22,190,57,215,123,4,22,18,54,62,111,235,229,
 | 
			
		||||
151,172,31,45,123,21,39,201,13,216,226,154,101,138,147,182,33,190,3,39,29,72,103,124,195,55,207,178,188,19,120,63,150,
 | 
			
		||||
247,130,73,235,119,67,185,103,249,126,224,179,163,188,35,28,90,191,39,68,85,181,249,174,16,21,117,46,240,249,106,84,149,
 | 
			
		||||
127,190,63,76,61,134,207,21,62,160,68,121,230,84,148,206,247,211,191,48,134,254,198,216,6,0,0};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (construct,   "<init>",   "()V") \
 | 
			
		||||
 METHOD (close,       "close",    "()V") \
 | 
			
		||||
 CALLBACK (FragmentOverlay::onActivityResultNative, "onActivityResultNative", "(JIILandroid/content/Intent;)V") \
 | 
			
		||||
 CALLBACK (FragmentOverlay::onCreateNative,         "onCreateNative",         "(JLandroid/os/Bundle;)V") \
 | 
			
		||||
 CALLBACK (FragmentOverlay::onStartNative,          "onStartNative",          "(J)V") \
 | 
			
		||||
 CALLBACK (FragmentOverlay::onRequestPermissionsResultNative, "onRequestPermissionsResultNative", "(JI[Ljava/lang/String;[I)V")
 | 
			
		||||
 | 
			
		||||
 DECLARE_JNI_CLASS_WITH_BYTECODE (JuceFragmentOverlay, "com/roli/juce/FragmentOverlay", 16, javaFragmentOverlay, sizeof(javaFragmentOverlay))
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (show,   "show",   "(Landroid/app/FragmentManager;Ljava/lang/String;)V")
 | 
			
		||||
 | 
			
		||||
 DECLARE_JNI_CLASS (AndroidDialogFragment, "android/app/DialogFragment")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
FragmentOverlay::FragmentOverlay()
 | 
			
		||||
    : native (LocalRef<jobject> (getEnv()->NewObject (JuceFragmentOverlay, JuceFragmentOverlay.construct)))
 | 
			
		||||
{}
 | 
			
		||||
 | 
			
		||||
FragmentOverlay::~FragmentOverlay()
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    env->CallVoidMethod (native.get(), JuceFragmentOverlay.close);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FragmentOverlay::open()
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> bundle (env->NewObject (AndroidBundle, AndroidBundle.constructor));
 | 
			
		||||
    env->CallVoidMethod (bundle.get(), AndroidBundle.putLong, javaString ("cppThis").get(), (jlong) this);
 | 
			
		||||
    env->CallVoidMethod (native.get(), AndroidFragment.setArguments, bundle.get());
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> fm (env->CallObjectMethod (getCurrentActivity().get(), AndroidActivity.getFragmentManager));
 | 
			
		||||
    env->CallVoidMethod (native.get(), AndroidDialogFragment.show, fm.get(), javaString ("FragmentOverlay").get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FragmentOverlay::onActivityResultNative (JNIEnv* env, jobject, jlong host,
 | 
			
		||||
                                              jint requestCode, jint resultCode, jobject data)
 | 
			
		||||
{
 | 
			
		||||
    if (auto* myself = reinterpret_cast<FragmentOverlay*> (host))
 | 
			
		||||
        myself->onActivityResult (requestCode, resultCode, LocalRef<jobject> (env->NewLocalRef (data)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FragmentOverlay::onCreateNative (JNIEnv* env, jobject, jlong host, jobject bundle)
 | 
			
		||||
{
 | 
			
		||||
    if (auto* myself = reinterpret_cast<FragmentOverlay*> (host))
 | 
			
		||||
        myself->onCreated (LocalRef<jobject> (env->NewLocalRef (bundle)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FragmentOverlay::onStartNative (JNIEnv*, jobject, jlong host)
 | 
			
		||||
{
 | 
			
		||||
    if (auto* myself = reinterpret_cast<FragmentOverlay*> (host))
 | 
			
		||||
        myself->onStart();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FragmentOverlay::onRequestPermissionsResultNative (JNIEnv* env, jobject, jlong host, jint requestCode,
 | 
			
		||||
                                                        jobjectArray jPermissions, jintArray jGrantResults)
 | 
			
		||||
{
 | 
			
		||||
    if (auto* myself = reinterpret_cast<FragmentOverlay*> (host))
 | 
			
		||||
    {
 | 
			
		||||
        Array<int> grantResults;
 | 
			
		||||
        int n = (jGrantResults != nullptr ? env->GetArrayLength (jGrantResults) : 0);
 | 
			
		||||
 | 
			
		||||
        if (n > 0)
 | 
			
		||||
        {
 | 
			
		||||
            auto* data = env->GetIntArrayElements (jGrantResults, 0);
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < n; ++i)
 | 
			
		||||
                grantResults.add (data[i]);
 | 
			
		||||
 | 
			
		||||
            env->ReleaseIntArrayElements (jGrantResults, data, 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        myself->onRequestPermissionsResult (requestCode,
 | 
			
		||||
                                            javaStringArrayToJuce (LocalRef<jobjectArray> (jPermissions)),
 | 
			
		||||
                                            grantResults);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jobject FragmentOverlay::getNativeHandle()
 | 
			
		||||
{
 | 
			
		||||
    return native.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class ActivityLauncher   : public FragmentOverlay
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    ActivityLauncher (const LocalRef<jobject>& intentToUse,
 | 
			
		||||
                      int requestCodeToUse,
 | 
			
		||||
                      std::function<void (int, int, LocalRef<jobject>)> && callbackToUse)
 | 
			
		||||
        : intent (intentToUse), requestCode (requestCodeToUse), callback (std::move (callbackToUse))
 | 
			
		||||
    {}
 | 
			
		||||
 | 
			
		||||
    void onStart() override
 | 
			
		||||
    {
 | 
			
		||||
        getEnv()->CallVoidMethod (getNativeHandle(), AndroidFragment.startActivityForResult,
 | 
			
		||||
                                  intent.get(), requestCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void onActivityResult (int activityRequestCode, int resultCode, LocalRef<jobject> data) override
 | 
			
		||||
    {
 | 
			
		||||
        if (callback)
 | 
			
		||||
            callback (activityRequestCode, resultCode, std::move (data));
 | 
			
		||||
 | 
			
		||||
        getEnv()->CallVoidMethod (getNativeHandle(), JuceFragmentOverlay.close);
 | 
			
		||||
        delete this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    GlobalRef intent;
 | 
			
		||||
    int requestCode;
 | 
			
		||||
    std::function<void (int, int, LocalRef<jobject>)> callback;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void startAndroidActivityForResult (const LocalRef<jobject>& intent, int requestCode,
 | 
			
		||||
                                    std::function<void (int, int, LocalRef<jobject>)> && callback)
 | 
			
		||||
{
 | 
			
		||||
    auto* activityLauncher = new ActivityLauncher (intent, requestCode, std::move (callback));
 | 
			
		||||
    activityLauncher->open();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
bool androidHasSystemFeature (const String& property)
 | 
			
		||||
{
 | 
			
		||||
    LocalRef<jobject> appContext (getAppContext());
 | 
			
		||||
 | 
			
		||||
    if (appContext != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        LocalRef<jobject> packageManager (env->CallObjectMethod (appContext.get(), AndroidContext.getPackageManager));
 | 
			
		||||
 | 
			
		||||
        if (packageManager != nullptr)
 | 
			
		||||
            return env->CallBooleanMethod (packageManager.get(),
 | 
			
		||||
                                           AndroidPackageManager.hasSystemFeature,
 | 
			
		||||
                                           javaString (property).get()) != 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // unable to get app's context
 | 
			
		||||
    jassertfalse;
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String audioManagerGetProperty (const String& property)
 | 
			
		||||
{
 | 
			
		||||
    if (getAndroidSDKVersion() >= 17)
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
        LocalRef<jobject> audioManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService,
 | 
			
		||||
                                                               javaString ("audio").get()));
 | 
			
		||||
 | 
			
		||||
        if (audioManager != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            LocalRef<jstring> jProperty (javaString (property));
 | 
			
		||||
 | 
			
		||||
            auto methodID = env->GetMethodID (AndroidAudioManager, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
 | 
			
		||||
 | 
			
		||||
            if (methodID != nullptr)
 | 
			
		||||
                return juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (audioManager.get(),
 | 
			
		||||
                                                                                       methodID,
 | 
			
		||||
                                                                                       javaString (property).get())));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -23,15 +23,177 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This byte-code is generated from native/java/com/roli/juce/JuceHTTPStream.java with min sdk version 16
 | 
			
		||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
 | 
			
		||||
static const uint8 javaJuceHttpStream[] =
 | 
			
		||||
{31,139,8,8,96,105,229,91,0,3,74,117,99,101,72,84,84,80,83,116,114,101,97,109,46,100,101,120,0,125,154,11,124,84,213,157,
 | 
			
		||||
199,255,231,222,185,119,30,153,153,220,76,30,147,132,60,38,33,132,4,18,38,32,40,146,128,200,75,32,65,17,6,20,146,181,29,
 | 
			
		||||
50,23,50,48,220,9,51,119,32,88,84,64,171,212,218,74,173,86,250,82,139,88,105,87,91,171,237,214,86,187,109,197,118,219,
 | 
			
		||||
218,90,109,245,179,174,187,125,91,119,237,167,187,186,218,118,221,246,227,102,127,231,49,153,137,77,33,124,239,255,127,
 | 
			
		||||
254,231,127,206,61,143,255,121,204,36,41,123,34,208,119,193,18,58,123,73,224,169,145,163,171,22,29,152,124,110,71,248,
 | 
			
		||||
214,145,193,207,157,62,245,139,197,215,175,242,156,233,36,26,39,162,137,237,139,35,164,254,253,114,14,209,11,36,237,139,
 | 
			
		||||
192,183,116,162,56,228,9,15,81,61,228,147,38,209,149,144,71,188,68,200,34,79,128,104,101,19,81,10,242,205,90,162,63,130,
 | 
			
		||||
183,193,95,192,255,1,173,142,200,0,33,80,5,162,160,27,92,12,54,131,173,224,42,48,2,118,1,27,92,11,174,3,199,192,205,224,
 | 
			
		||||
86,112,59,56,5,206,128,179,224,41,240,10,104,140,18,45,3,215,128,27,193,253,224,187,224,183,128,161,193,49,112,17,184,28,
 | 
			
		||||
236,6,71,193,221,224,33,240,44,120,5,188,13,194,13,68,237,96,0,92,5,246,129,227,224,62,240,8,248,1,248,37,120,19,248,27,
 | 
			
		||||
137,174,6,105,112,45,248,4,120,2,188,6,106,102,161,14,112,13,56,2,62,9,254,5,76,130,30,140,211,213,96,20,236,6,123,129,3,
 | 
			
		||||
242,224,125,224,40,184,13,156,4,31,5,119,131,79,130,123,193,253,224,113,240,36,248,30,120,17,252,28,188,10,94,7,255,3,
 | 
			
		||||
222,1,70,51,81,16,68,65,12,244,128,53,96,59,24,3,215,130,219,192,167,193,3,224,9,112,14,252,24,188,10,254,2,204,22,244,
 | 
			
		||||
17,88,160,17,116,128,249,96,17,184,8,108,2,127,7,28,112,61,184,25,220,13,206,128,175,128,239,128,31,129,127,6,175,129,55,
 | 
			
		||||
193,36,240,181,18,213,129,78,208,15,46,3,27,193,86,240,94,176,23,28,2,71,193,9,112,23,184,23,60,0,30,6,143,129,175,129,
 | 
			
		||||
103,193,207,192,239,192,155,224,207,128,98,232,59,168,4,115,64,63,216,14,118,131,125,96,28,28,4,71,192,205,224,36,184,23,
 | 
			
		||||
60,0,190,0,190,9,94,4,63,3,191,6,255,14,254,12,188,109,68,179,65,31,88,9,182,128,221,96,63,112,193,17,112,2,124,28,60,8,
 | 
			
		||||
190,8,190,14,190,15,126,14,222,2,122,59,226,2,52,130,118,208,11,150,131,203,64,2,100,64,1,92,7,110,5,31,1,167,64,16,221,
 | 
			
		||||
178,0,194,138,16,62,132,233,37,76,15,97,40,73,117,153,80,61,193,149,102,131,14,128,229,75,88,214,52,23,116,129,110,48,15,
 | 
			
		||||
204,7,61,160,23,44,32,185,166,251,192,66,181,206,47,0,139,193,18,112,33,184,8,44,5,23,131,126,48,0,86,128,75,192,74,112,
 | 
			
		||||
41,88,5,214,128,117,224,50,176,30,108,4,91,192,54,176,29,92,69,178,31,197,127,33,37,151,98,111,8,43,125,101,153,190,30,
 | 
			
		||||
122,165,210,55,215,202,254,51,149,230,251,143,1,70,96,143,148,213,203,245,98,249,6,165,187,202,167,88,87,173,242,91,170,
 | 
			
		||||
236,181,202,94,173,244,35,202,30,45,179,71,149,189,70,233,55,65,175,83,250,109,202,94,175,236,181,74,95,170,244,134,50,
 | 
			
		||||
157,207,219,157,181,178,28,215,63,165,222,213,92,214,254,150,50,189,181,76,111,47,211,103,151,245,133,207,239,67,170,126,
 | 
			
		||||
62,199,93,170,206,121,202,135,207,67,175,210,7,149,206,251,114,133,210,191,12,125,72,233,79,150,233,125,101,58,111,255,
 | 
			
		||||
38,165,63,13,125,179,210,249,248,95,174,244,31,150,249,252,91,173,60,27,122,213,248,23,235,121,165,86,198,196,2,213,158,
 | 
			
		||||
173,74,255,61,236,9,165,187,170,47,11,213,123,117,204,244,247,136,203,5,244,28,164,7,35,103,11,89,75,123,132,236,161,180,
 | 
			
		||||
144,97,250,176,144,221,244,77,226,241,209,68,41,33,165,159,161,252,12,140,88,65,200,249,116,147,144,81,186,69,201,15,8,
 | 
			
		||||
41,235,49,240,190,59,72,198,217,105,33,25,125,78,200,56,125,94,200,122,250,130,144,125,244,13,33,139,239,197,154,87,242,
 | 
			
		||||
135,64,163,42,186,75,180,191,141,76,33,189,244,30,33,131,66,122,208,30,83,200,86,250,146,40,215,41,210,188,221,59,84,59,
 | 
			
		||||
71,133,172,163,221,66,154,52,166,236,7,132,244,211,97,33,13,186,65,201,99,42,255,164,144,58,61,36,100,43,61,172,198,225,
 | 
			
		||||
31,136,175,153,118,241,158,16,118,11,46,195,200,79,10,233,17,254,149,72,31,18,114,30,253,68,196,93,5,237,83,241,119,187,
 | 
			
		||||
136,189,86,81,46,138,113,217,165,228,135,72,198,246,199,132,92,64,95,23,178,146,190,45,164,37,100,61,86,212,136,144,17,
 | 
			
		||||
218,47,164,44,87,143,145,146,82,150,175,87,254,13,234,61,13,88,101,35,66,134,233,25,226,123,225,108,97,111,68,254,19,196,
 | 
			
		||||
215,83,43,57,66,250,232,90,33,27,233,125,42,125,68,200,0,93,79,114,221,29,21,50,74,199,133,140,209,99,66,118,211,151,149,
 | 
			
		||||
252,138,178,127,85,200,14,122,92,200,57,244,53,226,107,85,142,87,51,118,85,41,235,233,140,144,178,93,173,24,247,247,11,
 | 
			
		||||
25,164,123,136,239,205,33,154,32,190,63,7,233,58,37,111,36,190,158,103,145,75,124,45,55,208,157,196,215,113,51,125,139,
 | 
			
		||||
248,94,221,66,25,33,155,232,227,196,215,116,47,189,87,200,160,168,103,190,26,143,249,248,145,233,110,122,148,248,158,46,
 | 
			
		||||
237,92,222,45,228,124,122,86,165,127,76,242,142,70,36,215,22,223,35,170,32,31,199,198,117,229,28,105,247,148,229,247,169,
 | 
			
		||||
252,151,144,191,87,229,243,120,102,84,218,47,121,254,219,200,63,174,242,121,253,63,197,193,243,50,248,77,135,244,253,131,
 | 
			
		||||
146,239,116,240,117,128,251,4,124,67,115,164,45,170,100,167,146,151,40,185,126,14,175,75,23,250,167,176,233,249,32,135,
 | 
			
		||||
145,24,209,24,141,91,179,68,132,242,92,94,223,153,118,185,71,182,179,32,109,139,49,156,83,22,37,98,68,7,44,175,136,112,
 | 
			
		||||
199,234,19,114,60,86,141,18,85,108,122,222,2,33,187,222,228,109,99,226,125,15,183,203,126,58,22,183,4,69,31,77,252,240,
 | 
			
		||||
188,71,219,229,121,34,219,48,204,52,26,214,12,26,214,61,52,236,209,105,216,48,197,202,225,237,50,232,71,237,242,60,76,
 | 
			
		||||
244,121,80,23,223,105,3,56,63,107,41,177,80,167,102,150,232,51,209,82,31,113,25,137,228,98,235,17,35,45,76,250,94,46,74,
 | 
			
		||||
105,200,247,10,233,88,189,194,98,40,139,1,75,237,84,205,155,197,104,132,80,122,46,158,37,91,215,159,144,90,40,83,76,156,
 | 
			
		||||
16,132,213,171,3,46,153,58,187,116,113,206,243,158,47,17,82,71,219,47,184,33,116,225,114,172,204,0,241,81,122,165,93,238,
 | 
			
		||||
221,188,207,62,140,114,15,180,77,240,28,62,230,71,170,86,216,121,138,231,205,71,106,163,72,85,96,95,226,55,129,32,59,96,
 | 
			
		||||
93,198,199,145,13,31,11,208,200,173,149,52,252,193,8,13,223,22,164,29,31,170,163,225,15,215,208,240,237,213,20,249,239,
 | 
			
		||||
29,199,162,120,179,69,59,142,90,120,75,21,13,31,13,209,182,27,43,41,113,83,132,18,239,15,210,22,92,253,19,183,132,200,
 | 
			
		||||
123,204,123,199,65,175,95,213,232,69,207,53,117,35,168,154,45,227,47,129,185,173,18,177,233,21,49,82,15,123,158,199,135,
 | 
			
		||||
175,25,49,176,12,235,216,177,6,240,142,160,175,213,215,68,222,155,90,61,77,20,241,57,177,11,113,18,56,177,197,244,7,60,
 | 
			
		||||
47,194,179,209,119,53,180,86,156,3,65,45,162,183,117,45,185,49,78,235,124,154,222,232,215,97,111,166,83,20,48,151,155,
 | 
			
		||||
237,194,230,224,114,236,163,128,111,201,205,141,34,29,241,59,184,34,158,50,131,140,167,94,242,155,204,137,197,80,34,232,
 | 
			
		||||
229,158,94,120,46,247,97,22,251,46,166,26,239,75,186,206,186,158,79,156,13,161,214,165,104,195,210,96,132,34,245,78,108,
 | 
			
		||||
9,215,67,60,70,23,34,166,130,134,131,15,47,47,34,21,69,108,85,121,66,34,42,60,232,85,136,26,253,126,244,172,6,245,207,
 | 
			
		||||
245,201,59,206,85,124,70,217,172,61,213,56,23,2,208,15,205,150,247,139,254,112,45,69,194,237,168,193,251,25,246,168,247,
 | 
			
		||||
156,247,5,246,91,239,159,124,94,139,52,95,21,249,252,17,58,24,48,197,216,93,28,254,175,201,234,112,95,161,235,119,33,22,
 | 
			
		||||
161,174,183,229,216,86,201,122,27,43,68,140,135,233,19,179,249,12,227,126,228,59,201,34,53,17,61,241,128,159,154,141,196,
 | 
			
		||||
233,10,180,110,16,30,1,109,41,214,71,139,193,22,85,106,145,200,120,44,136,115,36,168,13,159,14,83,226,116,13,69,204,196,
 | 
			
		||||
131,94,120,46,226,35,226,29,244,106,166,180,174,209,52,115,241,241,159,82,68,155,94,130,251,198,81,107,80,203,89,171,149,
 | 
			
		||||
92,195,165,153,179,86,96,133,242,183,61,77,57,235,18,232,65,214,202,218,160,95,202,35,5,246,55,38,115,214,42,165,255,110,
 | 
			
		||||
50,241,96,53,70,174,1,209,218,133,121,43,122,181,106,81,204,65,55,118,221,160,214,232,243,8,253,56,226,164,209,175,81,
 | 
			
		||||
177,116,171,86,67,7,98,98,39,214,74,190,84,244,213,28,107,158,104,153,19,107,199,154,43,175,185,209,27,128,87,19,229,208,
 | 
			
		||||
206,139,181,179,147,165,26,99,20,241,230,98,43,145,35,61,15,88,13,178,14,171,81,140,225,142,211,81,234,186,61,36,198,177,
 | 
			
		||||
155,70,38,67,106,44,22,137,220,37,199,219,228,104,122,18,159,149,86,140,132,57,104,106,30,233,213,11,175,153,243,101,125,
 | 
			
		||||
139,39,103,158,139,110,90,54,25,210,90,140,185,90,72,75,156,249,107,15,143,57,247,93,115,218,77,113,148,232,166,158,73,
 | 
			
		||||
212,188,170,155,188,147,124,255,240,99,87,170,22,123,168,87,220,131,121,236,116,136,61,171,90,124,214,184,1,233,199,196,
 | 
			
		||||
222,19,18,103,44,63,243,159,83,126,255,170,236,191,80,233,255,84,233,55,196,174,205,232,29,81,111,53,249,152,180,27,55,
 | 
			
		||||
132,30,101,191,103,55,116,126,149,189,206,232,247,140,94,103,215,119,190,95,19,70,47,198,129,159,41,175,206,150,159,119,
 | 
			
		||||
34,90,98,171,143,154,245,196,149,50,90,25,5,216,82,134,104,213,189,109,56,37,174,12,80,59,246,244,241,62,70,9,51,23,91,
 | 
			
		||||
139,207,56,216,113,174,172,69,126,2,207,156,181,65,236,65,173,44,76,93,175,133,88,139,62,151,97,119,106,235,250,141,120,
 | 
			
		||||
254,82,158,165,13,240,225,235,206,135,247,206,85,251,172,70,157,90,117,247,156,158,26,232,94,62,23,24,12,126,103,168,198,
 | 
			
		||||
135,155,72,232,32,171,64,127,2,214,197,150,33,44,149,214,75,161,16,243,193,182,133,89,116,97,243,255,78,114,61,193,48,31,
 | 
			
		||||
250,154,42,166,87,179,94,198,111,151,139,172,26,170,102,11,148,142,177,101,113,165,195,159,93,32,244,11,172,151,85,233,
 | 
			
		||||
106,106,212,55,32,26,59,112,195,14,176,151,194,44,80,178,35,150,251,230,208,194,112,160,242,162,215,159,82,254,124,222,
 | 
			
		||||
47,16,253,117,172,229,92,250,139,254,78,223,92,250,85,101,176,162,184,187,158,162,246,32,180,190,126,186,173,130,251,6,
 | 
			
		||||
17,27,139,238,184,103,234,189,17,93,104,30,148,180,58,49,70,1,143,211,55,155,22,122,132,21,177,226,195,213,178,57,34,189,
 | 
			
		||||
203,231,165,82,140,70,75,164,155,38,38,49,218,17,140,118,128,91,186,105,188,172,141,189,240,230,169,109,122,37,201,247,
 | 
			
		||||
68,164,52,130,66,110,49,235,100,218,27,66,204,84,83,241,36,225,182,97,212,192,235,95,57,41,107,230,250,170,73,18,247,243,
 | 
			
		||||
185,104,233,205,106,255,187,91,204,163,135,62,13,233,133,198,239,125,124,247,127,74,165,117,186,11,91,228,41,118,195,172,
 | 
			
		||||
239,176,187,24,169,91,12,209,137,14,185,135,110,141,85,137,207,177,69,251,135,59,100,172,108,137,69,113,103,183,166,206,
 | 
			
		||||
179,59,213,125,41,130,29,221,15,111,15,126,62,209,33,63,27,226,76,220,133,157,86,75,36,195,180,148,249,137,203,136,238,
 | 
			
		||||
172,172,163,3,55,6,168,69,91,142,246,108,221,85,69,199,60,79,172,218,9,89,73,24,51,13,99,198,186,222,98,36,239,86,30,181,
 | 
			
		||||
134,26,85,92,206,154,58,255,27,88,168,137,199,44,67,116,106,244,88,135,252,238,160,29,37,18,7,112,255,200,97,47,206,227,
 | 
			
		||||
157,46,110,47,133,90,58,104,242,154,18,57,209,158,3,21,20,169,118,98,67,136,171,8,27,134,119,139,214,110,54,34,50,230,
 | 
			
		||||
163,46,199,234,33,126,67,146,109,137,136,27,17,19,223,79,48,241,51,7,111,144,55,212,103,202,250,206,191,75,228,255,52,42,
 | 
			
		||||
73,55,32,199,242,8,228,77,1,121,103,44,126,47,192,207,175,147,1,57,166,167,32,239,83,249,229,119,95,158,111,170,122,252,
 | 
			
		||||
74,242,239,15,206,42,223,14,85,95,157,146,81,245,222,162,140,171,250,226,170,78,63,201,207,58,113,225,209,39,62,191,68,
 | 
			
		||||
85,217,5,101,109,43,239,131,69,53,194,110,168,54,151,202,71,133,125,158,242,227,159,65,25,201,123,167,108,67,84,148,105,
 | 
			
		||||
130,165,7,189,233,86,246,102,85,46,78,197,241,96,24,222,46,194,133,141,45,35,109,89,140,216,0,153,3,105,39,237,174,32,
 | 
			
		||||
109,69,63,121,86,244,119,111,39,107,117,214,113,236,81,55,157,117,98,118,46,151,205,81,24,22,215,118,220,222,33,219,217,
 | 
			
		||||
227,142,81,237,154,116,126,116,202,105,75,193,113,146,187,50,54,177,13,164,109,24,34,125,195,208,6,242,224,129,253,112,
 | 
			
		||||
35,85,111,44,140,218,235,19,137,205,91,221,156,157,220,191,96,111,242,96,146,216,16,105,112,210,185,143,54,132,34,67,120,
 | 
			
		||||
120,135,134,118,14,13,161,130,128,82,184,174,13,237,164,250,161,164,147,202,101,211,169,184,107,79,184,241,4,30,219,220,
 | 
			
		||||
116,38,223,79,177,161,209,236,254,120,46,155,73,199,247,226,53,241,233,239,234,88,216,79,139,207,239,49,99,71,250,169,
 | 
			
		||||
229,188,165,250,169,125,40,149,204,28,76,239,139,39,29,39,235,38,121,225,248,90,103,52,147,205,167,157,61,171,51,201,60,
 | 
			
		||||
218,54,251,124,62,155,108,119,44,155,226,47,250,107,167,13,104,79,78,85,210,54,67,254,38,123,255,46,229,96,195,165,121,6,
 | 
			
		||||
151,173,233,61,78,210,45,228,208,149,198,25,178,19,99,185,236,33,81,148,207,70,60,157,141,175,42,236,222,109,231,236,212,
 | 
			
		||||
6,103,188,224,22,123,89,59,149,189,225,138,181,19,163,246,56,47,60,205,92,238,93,55,101,190,162,224,150,217,235,165,61,
 | 
			
		||||
147,116,246,196,87,143,37,115,91,237,3,5,219,25,181,167,42,18,57,101,245,87,151,153,55,32,238,246,216,57,62,211,211,141,
 | 
			
		||||
185,92,97,220,181,83,101,197,106,202,61,224,32,103,50,82,102,189,98,215,94,76,244,116,207,210,156,151,123,162,237,152,
 | 
			
		||||
164,233,109,151,54,57,80,253,212,48,67,78,58,147,226,89,229,21,97,164,237,100,106,122,87,197,232,203,119,54,73,179,99,
 | 
			
		||||
187,241,245,174,59,190,109,203,80,105,237,245,83,184,148,139,156,169,214,168,116,185,167,234,81,1,171,66,246,221,205,162,
 | 
			
		||||
33,86,153,117,40,157,119,167,154,33,44,155,146,227,29,107,29,55,119,184,159,54,205,100,30,248,235,241,120,87,125,51,120,
 | 
			
		||||
172,192,15,85,78,175,110,186,97,171,237,242,176,47,25,176,254,70,11,185,28,182,151,248,234,100,38,35,118,146,214,243,231,
 | 
			
		||||
247,83,207,223,114,64,96,193,135,143,74,89,104,116,207,236,189,118,194,30,45,188,203,181,243,124,174,89,4,111,238,96,154,
 | 
			
		||||
199,110,236,252,126,249,169,185,125,183,199,186,2,95,151,197,165,55,115,110,63,13,156,47,123,224,188,203,22,19,208,49,
 | 
			
		||||
115,105,25,140,235,146,163,104,32,102,125,254,204,94,136,157,253,233,209,248,165,66,172,202,102,51,118,18,227,50,111,102,
 | 
			
		||||
231,76,118,116,95,62,190,197,134,158,75,58,238,16,146,253,228,135,16,83,176,140,216,118,210,182,99,143,223,142,61,126,59,
 | 
			
		||||
246,120,19,15,190,215,35,177,147,2,219,203,246,249,237,59,137,237,36,109,231,70,0,57,188,10,108,160,234,225,25,86,165,54,
 | 
			
		||||
226,80,32,57,58,106,231,243,29,125,125,125,84,33,245,117,153,228,158,60,121,147,169,84,14,41,50,147,227,227,182,147,34,
 | 
			
		||||
239,174,100,222,222,150,203,144,185,75,140,22,121,70,17,70,100,142,138,88,33,131,239,200,54,249,177,225,143,39,115,118,
 | 
			
		||||
34,75,94,117,34,80,160,116,52,80,93,73,79,100,75,167,6,89,163,24,83,215,46,45,198,162,69,78,21,31,144,162,165,116,128,20,
 | 
			
		||||
45,50,37,124,42,213,136,22,135,142,204,148,61,154,77,217,84,147,178,119,39,11,25,119,218,228,241,220,140,237,218,20,72,
 | 
			
		||||
149,154,82,155,154,241,84,174,158,102,150,213,144,63,149,85,77,38,102,147,193,167,239,48,249,132,192,2,133,166,226,152,
 | 
			
		||||
140,221,105,59,147,130,200,20,242,99,164,239,65,102,13,30,197,133,134,23,168,46,133,97,93,203,111,11,42,93,137,244,122,
 | 
			
		||||
188,202,206,173,227,53,228,133,67,89,156,146,137,244,160,125,88,56,150,159,24,84,1,195,102,156,146,98,24,120,238,22,59,
 | 
			
		||||
63,158,117,242,24,100,140,7,175,38,129,131,44,163,238,34,188,26,236,134,228,131,220,158,204,20,108,178,198,146,249,85,
 | 
			
		||||
136,72,213,70,27,33,0,203,229,184,53,80,197,152,104,209,80,218,177,17,41,50,145,167,160,82,18,217,109,136,132,240,24,118,
 | 
			
		||||
226,45,252,144,202,187,171,247,167,168,122,122,90,58,5,184,81,181,151,165,201,155,118,82,246,196,21,187,169,34,93,214,67,
 | 
			
		||||
95,218,81,77,170,72,231,215,78,140,37,11,121,151,183,38,157,23,227,64,102,58,143,126,186,60,151,75,89,179,47,173,246,111,
 | 
			
		||||
242,236,205,166,17,7,25,217,79,15,95,106,228,113,146,251,49,165,142,125,104,117,114,116,204,78,201,233,220,140,85,74,33,
 | 
			
		||||
110,44,133,97,5,146,165,96,66,2,175,16,146,47,5,143,195,71,67,119,10,104,36,30,171,14,187,24,143,26,104,235,178,153,76,
 | 
			
		||||
246,144,157,218,98,167,210,57,212,36,173,83,169,68,86,58,80,195,76,86,53,126,89,44,187,178,134,232,89,116,85,31,199,219,
 | 
			
		||||
61,120,44,20,207,69,228,27,47,206,48,215,220,53,73,55,73,161,162,38,43,242,136,72,245,230,16,233,88,193,84,153,83,97,176,
 | 
			
		||||
94,77,91,205,187,12,178,148,9,43,150,11,233,185,2,222,157,71,188,86,225,161,218,147,72,239,183,121,115,42,96,90,147,149,
 | 
			
		||||
81,71,141,121,30,152,121,151,135,139,236,72,169,247,220,113,42,22,195,121,30,139,201,84,177,22,75,164,69,96,200,251,28,
 | 
			
		||||
69,74,150,205,57,140,67,206,61,76,70,126,60,147,118,33,220,100,14,115,14,225,22,242,20,144,82,132,116,101,73,151,125,8,
 | 
			
		||||
230,69,0,169,163,194,204,23,118,237,71,13,126,200,188,216,2,41,228,142,149,111,57,213,60,249,238,93,167,100,44,219,102,
 | 
			
		||||
194,48,150,47,194,74,164,183,150,191,204,227,142,165,17,39,252,217,209,71,126,23,93,197,48,109,194,242,157,82,85,156,186,
 | 
			
		||||
89,185,31,83,133,91,182,28,235,248,57,144,223,157,205,237,183,83,151,151,69,160,23,59,139,104,129,89,112,68,36,87,28,76,
 | 
			
		||||
102,58,138,43,193,56,40,150,137,113,40,135,232,39,109,162,143,142,104,223,103,228,13,211,81,254,156,92,54,64,71,120,234,
 | 
			
		||||
30,205,56,199,190,203,190,195,158,198,231,109,227,159,216,188,117,222,240,64,47,255,71,159,215,224,112,98,249,96,186,117,
 | 
			
		||||
100,112,120,240,154,97,109,188,121,109,219,57,170,243,143,220,201,30,98,31,96,207,176,111,178,71,216,25,246,65,230,13,
 | 
			
		||||
107,127,209,250,39,38,14,107,199,175,59,35,10,247,14,12,14,177,72,37,89,236,5,228,210,106,115,228,163,236,239,217,9,246,
 | 
			
		||||
3,225,253,138,238,249,36,107,30,28,184,100,159,174,221,194,22,49,166,235,31,98,108,249,9,221,124,144,177,207,78,232,236,
 | 
			
		||||
80,253,9,221,251,19,86,159,214,14,246,27,204,240,106,225,126,195,28,94,48,180,96,80,55,62,203,172,129,75,12,163,197,208,
 | 
			
		||||
12,189,85,167,124,211,0,205,243,79,85,255,143,236,139,236,126,118,43,94,210,59,135,85,69,209,230,94,173,162,191,141,126,
 | 
			
		||||
141,94,111,92,126,139,214,219,163,237,104,214,42,14,61,62,113,61,107,180,60,204,109,239,127,154,53,84,178,250,208,138,95,
 | 
			
		||||
177,250,106,131,241,42,195,56,52,13,226,218,220,19,95,190,230,71,125,90,203,243,6,105,187,250,207,117,124,76,179,250,233,
 | 
			
		||||
32,187,131,121,155,81,89,109,143,182,191,249,229,182,143,172,68,15,40,95,211,218,98,144,208,58,91,233,78,62,202,189,186,
 | 
			
		||||
245,37,22,111,211,67,167,25,27,234,213,190,77,61,134,165,93,218,108,90,31,56,172,87,125,131,49,86,95,117,78,15,159,69,
 | 
			
		||||
159,245,202,207,49,182,224,156,238,223,87,255,65,189,226,83,172,126,167,238,187,166,101,167,30,188,135,181,12,233,218,
 | 
			
		||||
179,172,62,206,44,235,240,3,90,104,135,97,173,68,215,125,70,176,85,15,228,155,52,103,163,17,48,53,211,103,6,77,235,125,
 | 
			
		||||
172,165,246,162,86,51,64,111,241,169,163,183,197,243,29,241,252,185,166,189,231,51,24,149,54,157,238,195,80,15,78,180,
 | 
			
		||||
247,238,213,38,154,7,232,25,158,253,71,157,198,141,229,123,7,90,119,234,230,129,89,3,13,134,73,199,117,246,105,62,117,
 | 
			
		||||
154,151,61,207,26,131,154,79,219,233,161,83,172,69,11,32,221,100,68,123,163,203,163,70,180,35,234,213,42,184,1,3,88,84,
 | 
			
		||||
170,52,19,74,189,33,138,248,78,177,104,133,44,227,137,174,86,37,68,210,31,101,209,197,209,206,232,122,84,37,51,124,154,
 | 
			
		||||
95,214,224,41,86,213,73,26,211,232,100,140,225,255,209,163,158,71,106,53,246,92,45,251,118,236,190,58,50,24,211,144,41,
 | 
			
		||||
126,200,115,236,168,231,201,58,131,253,71,29,85,153,94,175,198,34,226,71,228,49,109,218,143,72,155,240,127,164,158,157,
 | 
			
		||||
136,189,212,160,69,222,104,96,13,127,152,197,34,63,108,98,145,163,237,190,170,147,179,141,170,183,59,89,213,201,185,172,
 | 
			
		||||
234,44,120,18,60,210,197,170,206,116,139,239,148,168,236,187,6,46,139,127,187,197,191,31,40,254,253,86,241,123,11,254,55,
 | 
			
		||||
92,252,59,146,226,223,113,241,239,20,138,127,203,101,82,233,239,185,116,75,254,254,141,127,15,195,98,242,119,191,143,240,
 | 
			
		||||
239,79,98,210,135,255,254,144,89,165,223,41,106,49,249,94,254,247,95,186,242,231,191,195,243,196,72,252,238,137,255,126,
 | 
			
		||||
144,84,89,241,123,71,75,182,149,255,173,217,255,3,11,139,181,139,164,38,0,0};
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (constructor, "<init>", "()V") \
 | 
			
		||||
 METHOD (toString, "toString", "()Ljava/lang/String;") \
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer");
 | 
			
		||||
DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)Lcom/roli/juce/JuceHTTPStream;") \
 | 
			
		||||
 METHOD (connect, "connect", "()Z") \
 | 
			
		||||
 METHOD (release, "release", "()V") \
 | 
			
		||||
 METHOD (read, "read", "([BI)I") \
 | 
			
		||||
@ -40,15 +202,15 @@ DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer");
 | 
			
		||||
 METHOD (isExhausted, "isExhausted", "()Z") \
 | 
			
		||||
 METHOD (setPosition, "setPosition", "(J)Z") \
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (HTTPStream, JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream");
 | 
			
		||||
DECLARE_JNI_CLASS_WITH_BYTECODE (HTTPStream, "com/roli/juce/JuceHTTPStream", 16, javaJuceHttpStream, sizeof(javaJuceHttpStream))
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 METHOD (close,     "close",     "()V") \
 | 
			
		||||
 METHOD (read,      "read",      "([BII)I") \
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidInputStream, "java/io/InputStream");
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidInputStream, "java/io/InputStream")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
@ -192,8 +354,8 @@ public:
 | 
			
		||||
                const ScopedLock lock (createStreamLock);
 | 
			
		||||
 | 
			
		||||
                if (! hasBeenCancelled)
 | 
			
		||||
                    stream = GlobalRef (LocalRef<jobject> (env->CallStaticObjectMethod (JuceAppActivity,
 | 
			
		||||
                                                                                        JuceAppActivity.createHTTPStream,
 | 
			
		||||
                    stream = GlobalRef (LocalRef<jobject> (env->CallStaticObjectMethod (HTTPStream,
 | 
			
		||||
                                                                                        HTTPStream.createHTTPStream,
 | 
			
		||||
                                                                                        javaString (address).get(),
 | 
			
		||||
                                                                                        (jboolean) isPost,
 | 
			
		||||
                                                                                        postDataArray,
 | 
			
		||||
@ -342,15 +504,22 @@ URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extra
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
static void addAddress (const sockaddr_in* addr_in, Array<IPAddress>& result)
 | 
			
		||||
{
 | 
			
		||||
    in_addr_t addr = addr_in->sin_addr.s_addr;
 | 
			
		||||
#if __ANDROID_API__ < 24   // Android support for getifadds was added in Android 7.0 (API 24) so the posix implementation does not apply
 | 
			
		||||
 | 
			
		||||
    if (addr != INADDR_NONE)
 | 
			
		||||
        result.addIfNotAlreadyThere (IPAddress (ntohl (addr)));
 | 
			
		||||
static IPAddress makeAddress (const sockaddr_in *addr_in)
 | 
			
		||||
{
 | 
			
		||||
    if (addr_in->sin_addr.s_addr == INADDR_NONE)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    return IPAddress (ntohl (addr_in->sin_addr.s_addr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void findIPAddresses (int sock, Array<IPAddress>& result)
 | 
			
		||||
struct InterfaceInfo
 | 
			
		||||
{
 | 
			
		||||
    IPAddress interfaceAddress, broadcastAddress;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static Array<InterfaceInfo> findIPAddresses (int dummySocket)
 | 
			
		||||
{
 | 
			
		||||
    ifconf cfg;
 | 
			
		||||
    HeapBlock<char> buffer;
 | 
			
		||||
@ -364,40 +533,66 @@ static void findIPAddresses (int sock, Array<IPAddress>& result)
 | 
			
		||||
        cfg.ifc_len = bufferSize;
 | 
			
		||||
        cfg.ifc_buf = buffer;
 | 
			
		||||
 | 
			
		||||
        if (ioctl (sock, SIOCGIFCONF, &cfg) < 0 && errno != EINVAL)
 | 
			
		||||
            return;
 | 
			
		||||
        if (ioctl (dummySocket, SIOCGIFCONF, &cfg) < 0 && errno != EINVAL)
 | 
			
		||||
            return {};
 | 
			
		||||
 | 
			
		||||
    } while (bufferSize < cfg.ifc_len + 2 * (int) (IFNAMSIZ + sizeof (struct sockaddr_in6)));
 | 
			
		||||
 | 
			
		||||
   #if JUCE_MAC || JUCE_IOS
 | 
			
		||||
    while (cfg.ifc_len >= (int) (IFNAMSIZ + sizeof (struct sockaddr_in)))
 | 
			
		||||
    {
 | 
			
		||||
        if (cfg.ifc_req->ifr_addr.sa_family == AF_INET) // Skip non-internet addresses
 | 
			
		||||
            addAddress ((const sockaddr_in*) &cfg.ifc_req->ifr_addr, result);
 | 
			
		||||
    Array<InterfaceInfo> result;
 | 
			
		||||
 | 
			
		||||
        cfg.ifc_len -= IFNAMSIZ + cfg.ifc_req->ifr_addr.sa_len;
 | 
			
		||||
        cfg.ifc_buf += IFNAMSIZ + cfg.ifc_req->ifr_addr.sa_len;
 | 
			
		||||
    }
 | 
			
		||||
   #else
 | 
			
		||||
    for (size_t i = 0; i < (size_t) cfg.ifc_len / (size_t) sizeof (struct ifreq); ++i)
 | 
			
		||||
    {
 | 
			
		||||
        const ifreq& item = cfg.ifc_req[i];
 | 
			
		||||
        auto& item = cfg.ifc_req[i];
 | 
			
		||||
 | 
			
		||||
        if (item.ifr_addr.sa_family == AF_INET)
 | 
			
		||||
            addAddress ((const sockaddr_in*) &item.ifr_addr, result);
 | 
			
		||||
        {
 | 
			
		||||
            InterfaceInfo info;
 | 
			
		||||
            info.interfaceAddress = makeAddress ((const sockaddr_in*) &item.ifr_addr);
 | 
			
		||||
 | 
			
		||||
            if (! info.interfaceAddress.isNull())
 | 
			
		||||
            {
 | 
			
		||||
                if (ioctl (dummySocket, SIOCGIFBRDADDR, &item) == 0)
 | 
			
		||||
                    info.broadcastAddress = makeAddress ((const sockaddr_in*) &item.ifr_broadaddr);
 | 
			
		||||
 | 
			
		||||
                result.add (info);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (item.ifr_addr.sa_family == AF_INET6)
 | 
			
		||||
        {
 | 
			
		||||
            // TODO: IPv6
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
   #endif
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Array<InterfaceInfo> findIPAddresses()
 | 
			
		||||
{
 | 
			
		||||
    auto dummySocket = socket (AF_INET, SOCK_DGRAM, 0); // a dummy socket to execute the IO control
 | 
			
		||||
 | 
			
		||||
    if (dummySocket < 0)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    auto result = findIPAddresses (dummySocket);
 | 
			
		||||
    ::close (dummySocket);
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IPAddress::findAllAddresses (Array<IPAddress>& result, bool /*includeIPv6*/)
 | 
			
		||||
{
 | 
			
		||||
    const int sock = socket (AF_INET, SOCK_DGRAM, 0); // a dummy socket to execute the IO control
 | 
			
		||||
 | 
			
		||||
    if (sock >= 0)
 | 
			
		||||
    {
 | 
			
		||||
        findIPAddresses (sock, result);
 | 
			
		||||
        ::close (sock);
 | 
			
		||||
    }
 | 
			
		||||
    for (auto& a : findIPAddresses())
 | 
			
		||||
        result.add (a.interfaceAddress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress& address)
 | 
			
		||||
{
 | 
			
		||||
    for (auto& a : findIPAddresses())
 | 
			
		||||
        if (a.interfaceAddress == address)
 | 
			
		||||
            return a.broadcastAddress;
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
} // namespace juce
 | 
			
		||||
 | 
			
		||||
@ -23,33 +23,185 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
static void handleAndroidCallback (bool permissionWasGranted, RuntimePermissions::Callback* callbackPtr)
 | 
			
		||||
//==============================================================================
 | 
			
		||||
static String jucePermissionToAndroidPermission (RuntimePermissions::PermissionID permission)
 | 
			
		||||
{
 | 
			
		||||
    if (callbackPtr == nullptr)
 | 
			
		||||
    switch (permission)
 | 
			
		||||
    {
 | 
			
		||||
        // got a nullptr passed in from java! this should never happen...
 | 
			
		||||
        jassertfalse;
 | 
			
		||||
        return;
 | 
			
		||||
        case RuntimePermissions::recordAudio:            return "android.permission.RECORD_AUDIO";
 | 
			
		||||
        case RuntimePermissions::bluetoothMidi:          return "android.permission.ACCESS_COARSE_LOCATION";
 | 
			
		||||
        case RuntimePermissions::readExternalStorage:    return "android.permission.READ_EXTERNAL_STORAGE";
 | 
			
		||||
        case RuntimePermissions::writeExternalStorage:   return "android.permission.WRITE_EXTERNAL_STORAGE";
 | 
			
		||||
        case RuntimePermissions::camera:                 return "android.permission.CAMERA";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<RuntimePermissions::Callback> uptr (callbackPtr);
 | 
			
		||||
 | 
			
		||||
    if (RuntimePermissions::Callback callbackObj = *uptr)
 | 
			
		||||
        callbackObj (permissionWasGranted);
 | 
			
		||||
    // invalid permission
 | 
			
		||||
    jassertfalse;
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME,
 | 
			
		||||
                   androidRuntimePermissionsCallback,
 | 
			
		||||
                   void, (JNIEnv* env, jobject, jboolean permissionsGranted, jlong callbackPtr))
 | 
			
		||||
static RuntimePermissions::PermissionID androidPermissionToJucePermission (const String& permission)
 | 
			
		||||
{
 | 
			
		||||
    setEnv (env);
 | 
			
		||||
    handleAndroidCallback (permissionsGranted != 0,
 | 
			
		||||
                           reinterpret_cast<RuntimePermissions::Callback*> (callbackPtr));
 | 
			
		||||
    if      (permission == "android.permission.RECORD_AUDIO")             return RuntimePermissions::recordAudio;
 | 
			
		||||
    else if (permission == "android.permission.ACCESS_COARSE_LOCATION")   return RuntimePermissions::bluetoothMidi;
 | 
			
		||||
    else if (permission == "android.permission.READ_EXTERNAL_STORAGE")    return RuntimePermissions::readExternalStorage;
 | 
			
		||||
    else if (permission == "android.permission.WRITE_EXTERNAL_STORAGE")   return RuntimePermissions::writeExternalStorage;
 | 
			
		||||
    else if (permission == "android.permission.CAMERA")                   return RuntimePermissions::camera;
 | 
			
		||||
 | 
			
		||||
    return static_cast<RuntimePermissions::PermissionID> (-1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
struct PermissionsRequest
 | 
			
		||||
{
 | 
			
		||||
    PermissionsRequest() {}
 | 
			
		||||
 | 
			
		||||
    // using "= default" on the following method triggers an internal compiler error
 | 
			
		||||
    // in Android NDK 17
 | 
			
		||||
    PermissionsRequest (const PermissionsRequest& o)
 | 
			
		||||
        : callback (o.callback), permission (o.permission)
 | 
			
		||||
    {}
 | 
			
		||||
 | 
			
		||||
    PermissionsRequest (PermissionsRequest&& o)
 | 
			
		||||
        : callback (std::move (o.callback)), permission (o.permission)
 | 
			
		||||
    {
 | 
			
		||||
        o.permission = static_cast<RuntimePermissions::PermissionID> (-1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PermissionsRequest (RuntimePermissions::Callback && callbackToUse,
 | 
			
		||||
                        RuntimePermissions::PermissionID permissionToRequest)
 | 
			
		||||
        : callback (std::move (callbackToUse)), permission (permissionToRequest)
 | 
			
		||||
    {}
 | 
			
		||||
 | 
			
		||||
    PermissionsRequest& operator= (const PermissionsRequest & o)
 | 
			
		||||
    {
 | 
			
		||||
        callback   = o.callback;
 | 
			
		||||
        permission = o.permission;
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PermissionsRequest& operator= (PermissionsRequest && o)
 | 
			
		||||
    {
 | 
			
		||||
        callback   = std::move (o.callback);
 | 
			
		||||
        permission = o.permission;
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RuntimePermissions::Callback callback;
 | 
			
		||||
    RuntimePermissions::PermissionID permission;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
struct PermissionsOverlay   : FragmentOverlay
 | 
			
		||||
{
 | 
			
		||||
    PermissionsOverlay (CriticalSection& cs) : overlayGuard (cs) {}
 | 
			
		||||
    ~PermissionsOverlay() {}
 | 
			
		||||
 | 
			
		||||
    struct PermissionResult
 | 
			
		||||
    {
 | 
			
		||||
        PermissionsRequest request;
 | 
			
		||||
        bool granted;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    void onStart() override    { onRequestPermissionsResult (0, {}, {}); }
 | 
			
		||||
 | 
			
		||||
    void onRequestPermissionsResult (int /*requestCode*/,
 | 
			
		||||
                                     const StringArray& permissions,
 | 
			
		||||
                                     const Array<int>& grantResults) override
 | 
			
		||||
    {
 | 
			
		||||
        std::vector<PermissionResult> results;
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            ScopedLock lock (overlayGuard);
 | 
			
		||||
 | 
			
		||||
            for (auto it = requests.begin(); it != requests.end();)
 | 
			
		||||
            {
 | 
			
		||||
                auto& request = *it;
 | 
			
		||||
 | 
			
		||||
                if (RuntimePermissions::isGranted (request.permission))
 | 
			
		||||
                {
 | 
			
		||||
                    results.push_back ({std::move (request), true});
 | 
			
		||||
                    it = requests.erase (it);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    ++it;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            auto n = permissions.size();
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < n; ++i)
 | 
			
		||||
            {
 | 
			
		||||
                auto permission = androidPermissionToJucePermission (permissions[i]);
 | 
			
		||||
                auto granted = (grantResults.getReference (i) == 0);
 | 
			
		||||
 | 
			
		||||
                for (auto it = requests.begin(); it != requests.end();)
 | 
			
		||||
                {
 | 
			
		||||
                    auto& request = *it;
 | 
			
		||||
 | 
			
		||||
                    if (request.permission == permission)
 | 
			
		||||
                    {
 | 
			
		||||
                        results.push_back ({std::move (request), granted});
 | 
			
		||||
                        it = requests.erase (it);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        ++it;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const auto& result : results)
 | 
			
		||||
            if (result.request.callback)
 | 
			
		||||
                result.request.callback (result.granted);
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            auto* env = getEnv();
 | 
			
		||||
            ScopedLock lock (overlayGuard);
 | 
			
		||||
 | 
			
		||||
            if (requests.size() > 0)
 | 
			
		||||
            {
 | 
			
		||||
                auto &request = requests.front();
 | 
			
		||||
 | 
			
		||||
                StringArray permissionsArray{
 | 
			
		||||
                        jucePermissionToAndroidPermission (request.permission)};
 | 
			
		||||
                auto jPermissionsArray = juceStringArrayToJava (permissionsArray);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                auto requestPermissionsMethodID
 | 
			
		||||
                    = env->GetMethodID(AndroidFragment, "requestPermissions", "([Ljava/lang/String;I)V");
 | 
			
		||||
 | 
			
		||||
                // this code should only be reached for SDKs >= 23, so this method should be
 | 
			
		||||
                // be available
 | 
			
		||||
                jassert(requestPermissionsMethodID != 0);
 | 
			
		||||
 | 
			
		||||
                env->CallVoidMethod (getNativeHandle(), requestPermissionsMethodID, jPermissionsArray.get (), 0);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                getSingleton() = nullptr;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static std::unique_ptr<PermissionsOverlay>& getSingleton()
 | 
			
		||||
    {
 | 
			
		||||
        static std::unique_ptr<PermissionsOverlay> instance;
 | 
			
		||||
        return instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CriticalSection& overlayGuard;
 | 
			
		||||
    std::vector<PermissionsRequest> requests;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
void RuntimePermissions::request (PermissionID permission, Callback callback)
 | 
			
		||||
{
 | 
			
		||||
    if (! android.activity.callBooleanMethod  (JuceAppActivity.isPermissionDeclaredInManifest, (jint) permission))
 | 
			
		||||
    auto requestedPermission = jucePermissionToAndroidPermission (permission);
 | 
			
		||||
 | 
			
		||||
    if (! isPermissionDeclaredInManifest (requestedPermission))
 | 
			
		||||
    {
 | 
			
		||||
        // Error! If you want to be able to request this runtime permission, you
 | 
			
		||||
        // also need to declare it in your app's manifest. You can do so via
 | 
			
		||||
@ -60,29 +212,50 @@ void RuntimePermissions::request (PermissionID permission, Callback callback)
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (JUCE_ANDROID_API_VERSION < 23)
 | 
			
		||||
    auto alreadyGranted = isGranted (permission);
 | 
			
		||||
 | 
			
		||||
    if (alreadyGranted || getAndroidSDKVersion() < 23)
 | 
			
		||||
    {
 | 
			
		||||
        // There is no runtime permission system on API level below 23. As long as the
 | 
			
		||||
        // permission is in the manifest (seems to be the case), we can simply ask Android
 | 
			
		||||
        // if the app has the permission, and then directly call through to the callback.
 | 
			
		||||
        callback (isGranted (permission));
 | 
			
		||||
        callback (alreadyGranted);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // we need to move the callback object to the heap so Java can keep track of the pointer
 | 
			
		||||
    // and asynchronously pass it back to us (to be called and then deleted)
 | 
			
		||||
    Callback* callbackPtr = new Callback (std::move (callback));
 | 
			
		||||
    android.activity.callVoidMethod (JuceAppActivity.requestRuntimePermission, permission, (jlong) callbackPtr);
 | 
			
		||||
    PermissionsRequest request (std::move (callback), permission);
 | 
			
		||||
 | 
			
		||||
    static CriticalSection overlayGuard;
 | 
			
		||||
    ScopedLock lock (overlayGuard);
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<PermissionsOverlay>& overlay = PermissionsOverlay::getSingleton();
 | 
			
		||||
 | 
			
		||||
    bool alreadyOpen = true;
 | 
			
		||||
 | 
			
		||||
    if (overlay == nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        overlay.reset (new PermissionsOverlay (overlayGuard));
 | 
			
		||||
        alreadyOpen = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    overlay->requests.push_back (std::move (request));
 | 
			
		||||
 | 
			
		||||
    if (! alreadyOpen)
 | 
			
		||||
        overlay->open();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RuntimePermissions::isRequired (PermissionID /*permission*/)
 | 
			
		||||
{
 | 
			
		||||
    return JUCE_ANDROID_API_VERSION >= 23;
 | 
			
		||||
    return getAndroidSDKVersion() >= 23;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RuntimePermissions::isGranted (PermissionID permission)
 | 
			
		||||
{
 | 
			
		||||
    return android.activity.callBooleanMethod (JuceAppActivity.isPermissionGranted, permission);
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    auto requestedPermission = jucePermissionToAndroidPermission (permission);
 | 
			
		||||
    int result = env->CallIntMethod (getAppContext().get(), AndroidContext.checkCallingOrSelfPermission,
 | 
			
		||||
                                     javaString (requestedPermission).get());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return result == 0 /* PERMISSION_GRANTED */;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace juce
 | 
			
		||||
 | 
			
		||||
@ -23,319 +23,21 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
 STATICMETHOD (newProxyInstance, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;") \
 | 
			
		||||
 | 
			
		||||
 DECLARE_JNI_CLASS (JavaProxy, "java/lang/reflect/Proxy");
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
JNIClassBase::JNIClassBase (const char* cp)   : classPath (cp), classRef (0)
 | 
			
		||||
{
 | 
			
		||||
    getClasses().add (this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
JNIClassBase::~JNIClassBase()
 | 
			
		||||
{
 | 
			
		||||
    getClasses().removeFirstMatchingValue (this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Array<JNIClassBase*>& JNIClassBase::getClasses()
 | 
			
		||||
{
 | 
			
		||||
    static Array<JNIClassBase*> classes;
 | 
			
		||||
    return classes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::initialise (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    classRef = (jclass) env->NewGlobalRef (LocalRef<jobject> (env->FindClass (classPath)));
 | 
			
		||||
    jassert (classRef != 0);
 | 
			
		||||
 | 
			
		||||
    initialiseFields (env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::release (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    env->DeleteGlobalRef (classRef);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::initialiseAllClasses (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    const Array<JNIClassBase*>& classes = getClasses();
 | 
			
		||||
    for (int i = classes.size(); --i >= 0;)
 | 
			
		||||
        classes.getUnchecked(i)->initialise (env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNIClassBase::releaseAllClasses (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    const Array<JNIClassBase*>& classes = getClasses();
 | 
			
		||||
    for (int i = classes.size(); --i >= 0;)
 | 
			
		||||
        classes.getUnchecked(i)->release (env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jmethodID JNIClassBase::resolveMethod (JNIEnv* env, const char* methodName, const char* params)
 | 
			
		||||
{
 | 
			
		||||
    jmethodID m = env->GetMethodID (classRef, methodName, params);
 | 
			
		||||
    jassert (m != 0);
 | 
			
		||||
    return m;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params)
 | 
			
		||||
{
 | 
			
		||||
    jmethodID m = env->GetStaticMethodID (classRef, methodName, params);
 | 
			
		||||
    jassert (m != 0);
 | 
			
		||||
    return m;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature)
 | 
			
		||||
{
 | 
			
		||||
    jfieldID f = env->GetFieldID (classRef, fieldName, signature);
 | 
			
		||||
    jassert (f != 0);
 | 
			
		||||
    return f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature)
 | 
			
		||||
{
 | 
			
		||||
    jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature);
 | 
			
		||||
    jassert (f != 0);
 | 
			
		||||
    return f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
 | 
			
		||||
                                       const StringArray& interfaceNames,
 | 
			
		||||
                                       LocalRef<jobject> subclass)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    implementer->javaSubClass = GlobalRef (subclass);
 | 
			
		||||
 | 
			
		||||
    // you need to override at least one interface
 | 
			
		||||
    jassert (interfaceNames.size() > 0);
 | 
			
		||||
 | 
			
		||||
    auto classArray = LocalRef<jobject> (env->NewObjectArray (interfaceNames.size(), JavaClass, nullptr));
 | 
			
		||||
    LocalRef<jobject> classLoader;
 | 
			
		||||
 | 
			
		||||
    for (auto i = 0; i < interfaceNames.size(); ++i)
 | 
			
		||||
    {
 | 
			
		||||
        auto aClass = LocalRef<jobject> (env->FindClass (interfaceNames[i].toRawUTF8()));
 | 
			
		||||
 | 
			
		||||
        if (aClass != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            if (i == 0)
 | 
			
		||||
                classLoader = LocalRef<jobject> (env->CallObjectMethod (aClass, JavaClass.getClassLoader));
 | 
			
		||||
 | 
			
		||||
            env->SetObjectArrayElement ((jobjectArray) classArray.get(), i, aClass);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            // interface class not found
 | 
			
		||||
            jassertfalse;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto invocationHandler = LocalRef<jobject> (env->CallObjectMethod (android.activity,
 | 
			
		||||
                                                                       JuceAppActivity.createInvocationHandler,
 | 
			
		||||
                                                                       reinterpret_cast<jlong> (implementer)));
 | 
			
		||||
 | 
			
		||||
    // CreateJavaInterface() is expected to be called just once for a given implementer
 | 
			
		||||
    jassert (implementer->invocationHandler == nullptr);
 | 
			
		||||
 | 
			
		||||
    implementer->invocationHandler = GlobalRef (invocationHandler);
 | 
			
		||||
 | 
			
		||||
    return LocalRef<jobject> (env->CallStaticObjectMethod (JavaProxy, JavaProxy.newProxyInstance,
 | 
			
		||||
                                                           classLoader.get(), classArray.get(),
 | 
			
		||||
                                                           invocationHandler.get()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
 | 
			
		||||
                                       const StringArray& interfaceNames)
 | 
			
		||||
{
 | 
			
		||||
    return CreateJavaInterface (implementer, interfaceNames,
 | 
			
		||||
                                LocalRef<jobject> (getEnv()->NewObject (JavaObject,
 | 
			
		||||
                                                                        JavaObject.constructor)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer,
 | 
			
		||||
                                       const String& interfaceName)
 | 
			
		||||
{
 | 
			
		||||
    return CreateJavaInterface (implementer, StringArray (interfaceName));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AndroidInterfaceImplementer::~AndroidInterfaceImplementer()
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    if (invocationHandler != nullptr)
 | 
			
		||||
        getEnv()->CallVoidMethod (android.activity,
 | 
			
		||||
                                  JuceAppActivity.invocationHandlerContextDeleted,
 | 
			
		||||
                                  invocationHandler.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jobject AndroidInterfaceImplementer::invoke (jobject /*proxy*/, jobject method, jobjectArray args)
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
    return env->CallObjectMethod (method, JavaMethod.invoke, javaSubClass.get(), args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jobject juce_invokeImplementer (JNIEnv* env, jlong thisPtr, jobject proxy, jobject method, jobjectArray args)
 | 
			
		||||
{
 | 
			
		||||
    setEnv (env);
 | 
			
		||||
    return reinterpret_cast<AndroidInterfaceImplementer*> (thisPtr)->invoke (proxy, method, args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void juce_dispatchDelete (JNIEnv* env, jlong thisPtr)
 | 
			
		||||
{
 | 
			
		||||
    setEnv (env);
 | 
			
		||||
    delete reinterpret_cast<AndroidInterfaceImplementer*> (thisPtr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeInvocationHandler), dispatchInvoke,
 | 
			
		||||
                   jobject, (JNIEnv* env, jobject /*object*/, jlong thisPtr, jobject proxy, jobject method, jobjectArray args))
 | 
			
		||||
{
 | 
			
		||||
    return juce_invokeImplementer (env, thisPtr, proxy, method, args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeInvocationHandler), dispatchFinalize,
 | 
			
		||||
                   void, (JNIEnv* env, jobject /*object*/, jlong thisPtr))
 | 
			
		||||
{
 | 
			
		||||
    juce_dispatchDelete (env, thisPtr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
JavaVM* androidJNIJavaVM = nullptr;
 | 
			
		||||
 | 
			
		||||
class JniEnvThreadHolder
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    static JniEnvThreadHolder& getInstance() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        // You cann only use JNI functions AFTER JNI_OnLoad was called
 | 
			
		||||
        jassert (androidJNIJavaVM != nullptr);
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (instance == nullptr)
 | 
			
		||||
                instance = new JniEnvThreadHolder;
 | 
			
		||||
        }
 | 
			
		||||
        catch (...)
 | 
			
		||||
        {
 | 
			
		||||
            jassertfalse;
 | 
			
		||||
            std::terminate();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return *instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static JNIEnv* getEnv()   { return reinterpret_cast<JNIEnv*> (pthread_getspecific (getInstance().threadKey)); }
 | 
			
		||||
 | 
			
		||||
    static void setEnv (JNIEnv* env)
 | 
			
		||||
    {
 | 
			
		||||
        // env must not be a nullptr
 | 
			
		||||
        jassert (env != nullptr);
 | 
			
		||||
 | 
			
		||||
       #if JUCE_DEBUG
 | 
			
		||||
        JNIEnv* oldenv = reinterpret_cast<JNIEnv*> (pthread_getspecific (getInstance().threadKey));
 | 
			
		||||
 | 
			
		||||
        // This thread is already attached to the JavaVM and you trying to attach
 | 
			
		||||
        // it to a different instance of the VM.
 | 
			
		||||
        jassert (oldenv == nullptr || oldenv == env);
 | 
			
		||||
       #endif
 | 
			
		||||
 | 
			
		||||
        pthread_setspecific (getInstance().threadKey, env);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    pthread_key_t threadKey;
 | 
			
		||||
 | 
			
		||||
    static void threadDetach (void* p)
 | 
			
		||||
    {
 | 
			
		||||
        if (JNIEnv* env = reinterpret_cast<JNIEnv*> (p))
 | 
			
		||||
        {
 | 
			
		||||
            ignoreUnused (env);
 | 
			
		||||
 | 
			
		||||
            androidJNIJavaVM->DetachCurrentThread();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    JniEnvThreadHolder()
 | 
			
		||||
    {
 | 
			
		||||
        pthread_key_create (&threadKey, threadDetach);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static JniEnvThreadHolder* instance;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
JniEnvThreadHolder* JniEnvThreadHolder::instance = nullptr;
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
JNIEnv* attachAndroidJNI() noexcept
 | 
			
		||||
{
 | 
			
		||||
    auto* env = JniEnvThreadHolder::getEnv();
 | 
			
		||||
 | 
			
		||||
    if (env == nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        androidJNIJavaVM->AttachCurrentThread (&env, nullptr);
 | 
			
		||||
        setEnv (env);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return env;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
JNIEnv* getEnv() noexcept
 | 
			
		||||
{
 | 
			
		||||
    auto* env = JniEnvThreadHolder::getEnv();
 | 
			
		||||
 | 
			
		||||
    // You are trying to use a JUCE function on a thread that was not created by JUCE.
 | 
			
		||||
    // You need to first call setEnv on this thread before using JUCE
 | 
			
		||||
    jassert (env != nullptr);
 | 
			
		||||
 | 
			
		||||
    return env;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setEnv (JNIEnv* env) noexcept   { JniEnvThreadHolder::setEnv (env); }
 | 
			
		||||
 | 
			
		||||
extern "C" jint JNI_OnLoad (JavaVM* vm, void*)
 | 
			
		||||
{
 | 
			
		||||
    // Huh? JNI_OnLoad was called two times!
 | 
			
		||||
    jassert (androidJNIJavaVM == nullptr);
 | 
			
		||||
 | 
			
		||||
    androidJNIJavaVM = vm;
 | 
			
		||||
    return JNI_VERSION_1_2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0), dpi (160)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dataDir)
 | 
			
		||||
{
 | 
			
		||||
    setEnv (env);
 | 
			
		||||
 | 
			
		||||
    screenWidth = screenHeight = 0;
 | 
			
		||||
    dpi = 160;
 | 
			
		||||
    JNIClassBase::initialiseAllClasses (env);
 | 
			
		||||
 | 
			
		||||
    activity = GlobalRef (act);
 | 
			
		||||
    appFile = juceString (env, file);
 | 
			
		||||
    appDataDir = juceString (env, dataDir);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AndroidSystem::shutdown (JNIEnv* env)
 | 
			
		||||
{
 | 
			
		||||
    activity.clear();
 | 
			
		||||
 | 
			
		||||
    JNIClassBase::releaseAllClasses (env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AndroidSystem android;
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
namespace AndroidStatsHelpers
 | 
			
		||||
{
 | 
			
		||||
    #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 | 
			
		||||
    #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
     STATICMETHOD (getProperty, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;")
 | 
			
		||||
 | 
			
		||||
    DECLARE_JNI_CLASS (SystemClass, "java/lang/System");
 | 
			
		||||
    DECLARE_JNI_CLASS (SystemClass, "java/lang/System")
 | 
			
		||||
    #undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
    #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
     STATICMETHOD (getDefault, "getDefault", "()Ljava/util/Locale;") \
 | 
			
		||||
     METHOD (getCountry, "getCountry", "()Ljava/lang/String;") \
 | 
			
		||||
     METHOD (getLanguage, "getLanguage", "()Ljava/lang/String;")
 | 
			
		||||
 | 
			
		||||
    DECLARE_JNI_CLASS (JavaLocale, "java/util/Locale")
 | 
			
		||||
    #undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
    static inline String getSystemProperty (const String& name)
 | 
			
		||||
@ -347,19 +49,19 @@ namespace AndroidStatsHelpers
 | 
			
		||||
 | 
			
		||||
    static inline String getLocaleValue (bool isRegion)
 | 
			
		||||
    {
 | 
			
		||||
        return juceString (LocalRef<jstring> ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity,
 | 
			
		||||
                                                                                          JuceAppActivity.getLocaleValue,
 | 
			
		||||
                                                                                          isRegion)));
 | 
			
		||||
    }
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
        LocalRef<jobject> locale (env->CallStaticObjectMethod (JavaLocale, JavaLocale.getDefault));
 | 
			
		||||
 | 
			
		||||
    #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD)
 | 
			
		||||
    DECLARE_JNI_CLASS (BuildClass, "android/os/Build");
 | 
			
		||||
    #undef JNI_CLASS_MEMBERS
 | 
			
		||||
        auto stringResult = isRegion ? env->CallObjectMethod (locale.get(), JavaLocale.getCountry)
 | 
			
		||||
                                     : env->CallObjectMethod (locale.get(), JavaLocale.getLanguage);
 | 
			
		||||
 | 
			
		||||
        return juceString (LocalRef<jstring> ((jstring) stringResult));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static inline String getAndroidOsBuildValue (const char* fieldName)
 | 
			
		||||
    {
 | 
			
		||||
        return juceString (LocalRef<jstring> ((jstring) getEnv()->GetStaticObjectField (
 | 
			
		||||
                            BuildClass, getEnv()->GetStaticFieldID (BuildClass, fieldName, "Ljava/lang/String;"))));
 | 
			
		||||
                            AndroidBuild, getEnv()->GetStaticFieldID (AndroidBuild, fieldName, "Ljava/lang/String;"))));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -404,7 +106,7 @@ String SystemStats::getCpuModel()
 | 
			
		||||
    return readPosixConfigFileValue ("/proc/cpuinfo", "Hardware");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int SystemStats::getCpuSpeedInMegaherz()
 | 
			
		||||
int SystemStats::getCpuSpeedInMegahertz()
 | 
			
		||||
{
 | 
			
		||||
    int maxFreqKHz = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,322 @@ namespace juce
 | 
			
		||||
    live in juce_posix_SharedCode.h!
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 | 
			
		||||
 FIELD (activityInfo, "activityInfo", "Landroid/content/pm/ActivityInfo;")
 | 
			
		||||
 | 
			
		||||
DECLARE_JNI_CLASS (AndroidResolveInfo, "android/content/pm/ResolveInfo")
 | 
			
		||||
#undef JNI_CLASS_MEMBERS
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
JavaVM* androidJNIJavaVM = nullptr;
 | 
			
		||||
jobject androidApkContext = nullptr;
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
JNIEnv* getEnv() noexcept
 | 
			
		||||
{
 | 
			
		||||
    if (androidJNIJavaVM != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        JNIEnv* env;
 | 
			
		||||
        androidJNIJavaVM->AttachCurrentThread (&env, nullptr);
 | 
			
		||||
 | 
			
		||||
        return env;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // You did not call Thread::initialiseJUCE which must be called at least once in your apk
 | 
			
		||||
    // before using any JUCE APIs. The Projucer will automatically generate java code
 | 
			
		||||
    // which will invoke Thread::initialiseJUCE for you.
 | 
			
		||||
    jassertfalse;
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void JNICALL juce_JavainitialiseJUCE (JNIEnv* env, jobject /*jclass*/, jobject context)
 | 
			
		||||
{
 | 
			
		||||
    Thread::initialiseJUCE (env, context);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" jint JNIEXPORT JNI_OnLoad (JavaVM* vm, void*)
 | 
			
		||||
{
 | 
			
		||||
    // Huh? JNI_OnLoad was called two times!
 | 
			
		||||
    jassert (androidJNIJavaVM == nullptr);
 | 
			
		||||
 | 
			
		||||
    androidJNIJavaVM = vm;
 | 
			
		||||
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
    // register the initialisation function
 | 
			
		||||
    auto juceJavaClass = env->FindClass("com/roli/juce/Java");
 | 
			
		||||
 | 
			
		||||
    if (juceJavaClass != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        JNINativeMethod method {"initialiseJUCE", "(Landroid/content/Context;)V",
 | 
			
		||||
                                reinterpret_cast<void*> (juce_JavainitialiseJUCE)};
 | 
			
		||||
 | 
			
		||||
        auto status = env->RegisterNatives (juceJavaClass, &method, 1);
 | 
			
		||||
        jassert (status == 0);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        // com.roli.juce.Java class not found. Apparently this project is a library
 | 
			
		||||
        // or was not generated by the Projucer. That's ok, the user will have to
 | 
			
		||||
        // call Thread::initialiseJUCE manually
 | 
			
		||||
        env->ExceptionClear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    JNIClassBase::initialiseAllClasses (env);
 | 
			
		||||
 | 
			
		||||
    return JNI_VERSION_1_2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class JuceActivityWatcher   : public ActivityLifecycleCallbacks
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    JuceActivityWatcher()
 | 
			
		||||
    {
 | 
			
		||||
        LocalRef<jobject> appContext (getAppContext());
 | 
			
		||||
 | 
			
		||||
        if (appContext != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
            myself = GlobalRef (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks"));
 | 
			
		||||
            env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, myself.get());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        checkActivityIsMain (androidApkContext);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~JuceActivityWatcher()
 | 
			
		||||
    {
 | 
			
		||||
        LocalRef<jobject> appContext (getAppContext());
 | 
			
		||||
 | 
			
		||||
        if (appContext != nullptr && myself != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
            env->CallVoidMethod (appContext.get(), AndroidApplication.unregisterActivityLifecycleCallbacks, myself.get());
 | 
			
		||||
            clear();
 | 
			
		||||
            myself.clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void onActivityStarted (jobject activity) override
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        checkActivityIsMain (activity);
 | 
			
		||||
 | 
			
		||||
        ScopedLock lock (currentActivityLock);
 | 
			
		||||
 | 
			
		||||
        if (currentActivity != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            // see Clarification June 2001 in JNI reference for why this is
 | 
			
		||||
            // necessary
 | 
			
		||||
            LocalRef<jobject> localStorage (env->NewLocalRef (currentActivity));
 | 
			
		||||
 | 
			
		||||
            if (env->IsSameObject (localStorage.get(), activity) != 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            env->DeleteWeakGlobalRef (currentActivity);
 | 
			
		||||
            currentActivity = nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (activity != nullptr)
 | 
			
		||||
            currentActivity = env->NewWeakGlobalRef (activity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void onActivityStopped (jobject activity) override
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        ScopedLock lock (currentActivityLock);
 | 
			
		||||
 | 
			
		||||
        if (currentActivity != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            // important that the comparison happens in this order
 | 
			
		||||
            // to avoid race condition where the weak reference becomes null
 | 
			
		||||
            // just after the first check
 | 
			
		||||
            if (env->IsSameObject (currentActivity, activity) != 0
 | 
			
		||||
                || env->IsSameObject (currentActivity, nullptr) != 0)
 | 
			
		||||
            {
 | 
			
		||||
                env->DeleteWeakGlobalRef (currentActivity);
 | 
			
		||||
                currentActivity = nullptr;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> getCurrent()
 | 
			
		||||
    {
 | 
			
		||||
        ScopedLock lock (currentActivityLock);
 | 
			
		||||
        return LocalRef<jobject> (getEnv()->NewLocalRef (currentActivity));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> getMain()
 | 
			
		||||
    {
 | 
			
		||||
        ScopedLock lock (currentActivityLock);
 | 
			
		||||
        return LocalRef<jobject> (getEnv()->NewLocalRef (mainActivity));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static JuceActivityWatcher& getInstance()
 | 
			
		||||
    {
 | 
			
		||||
        static JuceActivityWatcher activityWatcher;
 | 
			
		||||
        return activityWatcher;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void checkActivityIsMain (jobject context)
 | 
			
		||||
    {
 | 
			
		||||
        auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
        ScopedLock lock (currentActivityLock);
 | 
			
		||||
 | 
			
		||||
        if (mainActivity != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            if (env->IsSameObject (mainActivity, nullptr) != 0)
 | 
			
		||||
            {
 | 
			
		||||
                env->DeleteWeakGlobalRef (mainActivity);
 | 
			
		||||
                mainActivity = nullptr;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mainActivity == nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            LocalRef<jobject> appContext (getAppContext());
 | 
			
		||||
            auto mainActivityPath = getMainActivityClassPath();
 | 
			
		||||
 | 
			
		||||
            if (mainActivityPath.isNotEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                auto clasz = env->GetObjectClass (context);
 | 
			
		||||
                auto activityPath = juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (clasz, JavaClass.getName)));
 | 
			
		||||
 | 
			
		||||
                // This may be problematic for apps which use several activities with the same type. We just
 | 
			
		||||
                // assume that the very first activity of this type is the main one
 | 
			
		||||
                if (activityPath == mainActivityPath)
 | 
			
		||||
                    mainActivity = env->NewWeakGlobalRef (context);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static String getMainActivityClassPath()
 | 
			
		||||
    {
 | 
			
		||||
        static String mainActivityClassPath;
 | 
			
		||||
 | 
			
		||||
        if (mainActivityClassPath.isEmpty())
 | 
			
		||||
        {
 | 
			
		||||
            LocalRef<jobject> appContext (getAppContext());
 | 
			
		||||
 | 
			
		||||
            if (appContext != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                auto* env = getEnv();
 | 
			
		||||
 | 
			
		||||
                LocalRef<jobject> pkgManager (env->CallObjectMethod (appContext.get(), AndroidContext.getPackageManager));
 | 
			
		||||
                LocalRef<jstring> pkgName ((jstring) env->CallObjectMethod (appContext.get(), AndroidContext.getPackageName));
 | 
			
		||||
 | 
			
		||||
                LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructWithString,
 | 
			
		||||
                                                          javaString ("android.intent.action.MAIN").get()));
 | 
			
		||||
 | 
			
		||||
                intent = LocalRef<jobject> (env->CallObjectMethod (intent.get(),
 | 
			
		||||
                                                                   AndroidIntent.setPackage,
 | 
			
		||||
                                                                   pkgName.get()));
 | 
			
		||||
 | 
			
		||||
                LocalRef<jobject> resolveInfo (env->CallObjectMethod (pkgManager.get(), AndroidPackageManager.resolveActivity, intent.get(), 0));
 | 
			
		||||
 | 
			
		||||
                if (resolveInfo != nullptr)
 | 
			
		||||
                {
 | 
			
		||||
                    LocalRef<jobject> activityInfo (env->GetObjectField (resolveInfo.get(), AndroidResolveInfo.activityInfo));
 | 
			
		||||
                    LocalRef<jstring> jName ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.name));
 | 
			
		||||
                    LocalRef<jstring> jPackage ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.packageName));
 | 
			
		||||
 | 
			
		||||
                    mainActivityClassPath = juceString (jName);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return mainActivityClassPath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    GlobalRef myself;
 | 
			
		||||
    CriticalSection currentActivityLock;
 | 
			
		||||
    jweak currentActivity = nullptr;
 | 
			
		||||
    jweak mainActivity    = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
#if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID
 | 
			
		||||
void juce_juceEventsAndroidStartApp();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void Thread::initialiseJUCE (void* jniEnv, void* context)
 | 
			
		||||
{
 | 
			
		||||
    static CriticalSection cs;
 | 
			
		||||
    ScopedLock lock (cs);
 | 
			
		||||
 | 
			
		||||
    // jniEnv and context should not be null!
 | 
			
		||||
    jassert (jniEnv != nullptr && context != nullptr);
 | 
			
		||||
 | 
			
		||||
    auto* env = static_cast<JNIEnv*> (jniEnv);
 | 
			
		||||
 | 
			
		||||
    if (androidJNIJavaVM == nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        JavaVM* javaVM = nullptr;
 | 
			
		||||
 | 
			
		||||
        auto status = env->GetJavaVM (&javaVM);
 | 
			
		||||
        jassert (status == 0 && javaVM != nullptr);
 | 
			
		||||
 | 
			
		||||
        androidJNIJavaVM = javaVM;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static bool firstCall = true;
 | 
			
		||||
 | 
			
		||||
    if (firstCall)
 | 
			
		||||
    {
 | 
			
		||||
        firstCall = false;
 | 
			
		||||
 | 
			
		||||
        // if we ever support unloading then this should probably be a weak reference
 | 
			
		||||
        androidApkContext = env->NewGlobalRef (static_cast<jobject> (context));
 | 
			
		||||
        JuceActivityWatcher::getInstance();
 | 
			
		||||
 | 
			
		||||
       #if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID
 | 
			
		||||
        juce_juceEventsAndroidStartApp();
 | 
			
		||||
       #endif
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
LocalRef<jobject> getAppContext() noexcept
 | 
			
		||||
{
 | 
			
		||||
    auto* env = getEnv();
 | 
			
		||||
    auto context = androidApkContext;
 | 
			
		||||
 | 
			
		||||
    // You did not call Thread::initialiseJUCE which must be called at least once in your apk
 | 
			
		||||
    // before using any JUCE APIs. The Projucer will automatically generate java code
 | 
			
		||||
    // which will invoke Thread::initialiseJUCE for you.
 | 
			
		||||
    jassert (env != nullptr && context != nullptr);
 | 
			
		||||
 | 
			
		||||
    if (context == nullptr)
 | 
			
		||||
        return LocalRef<jobject>();
 | 
			
		||||
 | 
			
		||||
    if (env->IsInstanceOf (context, AndroidApplication) != 0)
 | 
			
		||||
        return LocalRef<jobject> (env->NewLocalRef (context));
 | 
			
		||||
 | 
			
		||||
    LocalRef<jobject> applicationContext (env->CallObjectMethod (context, AndroidContext.getApplicationContext));
 | 
			
		||||
 | 
			
		||||
    if (applicationContext == nullptr)
 | 
			
		||||
        return LocalRef<jobject> (env->NewLocalRef (context));
 | 
			
		||||
 | 
			
		||||
    return applicationContext;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LocalRef<jobject> getCurrentActivity() noexcept
 | 
			
		||||
{
 | 
			
		||||
    return JuceActivityWatcher::getInstance().getCurrent();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LocalRef<jobject> getMainActivity() noexcept
 | 
			
		||||
{
 | 
			
		||||
    return JuceActivityWatcher::getInstance().getMain();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// sets the process to 0=low priority, 1=normal, 2=high, 3=realtime
 | 
			
		||||
JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior)
 | 
			
		||||
@ -74,4 +390,6 @@ JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept
 | 
			
		||||
JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {}
 | 
			
		||||
JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
} // namespace juce
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,90 @@
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
struct CURLSymbols
 | 
			
		||||
{
 | 
			
		||||
    CURL* (*curl_easy_init) (void);
 | 
			
		||||
    CURLcode (*curl_easy_setopt) (CURL *curl, CURLoption option, ...);
 | 
			
		||||
    void (*curl_easy_cleanup) (CURL *curl);
 | 
			
		||||
    CURLcode (*curl_easy_getinfo) (CURL *curl, CURLINFO info, ...);
 | 
			
		||||
    CURLMcode (*curl_multi_add_handle) (CURLM *multi_handle, CURL *curl_handle);
 | 
			
		||||
    CURLMcode (*curl_multi_cleanup) (CURLM *multi_handle);
 | 
			
		||||
    CURLMcode (*curl_multi_fdset) (CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd);
 | 
			
		||||
    CURLMsg* (*curl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue);
 | 
			
		||||
    CURLM* (*curl_multi_init) (void);
 | 
			
		||||
    CURLMcode (*curl_multi_perform) (CURLM *multi_handle, int *running_handles);
 | 
			
		||||
    CURLMcode (*curl_multi_remove_handle) (CURLM *multi_handle, CURL *curl_handle);
 | 
			
		||||
    CURLMcode (*curl_multi_timeout) (CURLM *multi_handle, long *milliseconds);
 | 
			
		||||
    struct curl_slist* (*curl_slist_append) (struct curl_slist *, const char *);
 | 
			
		||||
    void (*curl_slist_free_all) (struct curl_slist *);
 | 
			
		||||
    curl_version_info_data* (*curl_version_info) (CURLversion);
 | 
			
		||||
 | 
			
		||||
    static std::unique_ptr<CURLSymbols> create()
 | 
			
		||||
    {
 | 
			
		||||
        std::unique_ptr<CURLSymbols> symbols (new CURLSymbols);
 | 
			
		||||
 | 
			
		||||
       #if JUCE_LOAD_CURL_SYMBOLS_LAZILY
 | 
			
		||||
        const ScopedLock sl (getLibcurlLock());
 | 
			
		||||
        #define JUCE_INIT_CURL_SYMBOL(name)  if (! symbols->loadSymbol (symbols->name, #name)) return nullptr;
 | 
			
		||||
       #else
 | 
			
		||||
        #define JUCE_INIT_CURL_SYMBOL(name)  symbols->name = ::name;
 | 
			
		||||
       #endif
 | 
			
		||||
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_easy_init)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_easy_setopt)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_easy_cleanup)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_easy_getinfo)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_add_handle)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_cleanup)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_fdset)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_info_read)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_init)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_perform)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_remove_handle)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_multi_timeout)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_slist_append)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_slist_free_all)
 | 
			
		||||
        JUCE_INIT_CURL_SYMBOL (curl_version_info)
 | 
			
		||||
 | 
			
		||||
        return symbols;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // liburl's curl_multi_init calls curl_global_init which is not thread safe
 | 
			
		||||
    // so we need to get a lock during calls to curl_multi_init and curl_multi_cleanup
 | 
			
		||||
    static CriticalSection& getLibcurlLock() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        static CriticalSection cs;
 | 
			
		||||
        return cs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    CURLSymbols() = default;
 | 
			
		||||
 | 
			
		||||
   #if JUCE_LOAD_CURL_SYMBOLS_LAZILY
 | 
			
		||||
    static DynamicLibrary& getLibcurl()
 | 
			
		||||
    {
 | 
			
		||||
        const ScopedLock sl (getLibcurlLock());
 | 
			
		||||
        static DynamicLibrary libcurl;
 | 
			
		||||
 | 
			
		||||
        if (libcurl.getNativeHandle() == nullptr)
 | 
			
		||||
            for (auto libName : { "libcurl.so", "libcurl.so.4", "libcurl.so.3" })
 | 
			
		||||
                if (libcurl.open (libName))
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
        return libcurl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename FuncPtr>
 | 
			
		||||
    bool loadSymbol (FuncPtr& dst, const char* name)
 | 
			
		||||
    {
 | 
			
		||||
        dst = reinterpret_cast<FuncPtr> (getLibcurl().getFunction (name));
 | 
			
		||||
        return (dst != nullptr);
 | 
			
		||||
    }
 | 
			
		||||
   #endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
class WebInputStream::Pimpl
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
@ -30,14 +114,19 @@ public:
 | 
			
		||||
        : owner (ownerStream), url (urlToCopy), isPost (shouldUsePost),
 | 
			
		||||
          httpRequest (isPost ? "POST" : "GET")
 | 
			
		||||
    {
 | 
			
		||||
        multi = curl_multi_init();
 | 
			
		||||
        jassert (symbols); // Unable to load libcurl!
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            const ScopedLock sl (CURLSymbols::getLibcurlLock());
 | 
			
		||||
            multi = symbols->curl_multi_init();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (multi != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            curl = curl_easy_init();
 | 
			
		||||
            curl = symbols->curl_easy_init();
 | 
			
		||||
 | 
			
		||||
            if (curl != nullptr)
 | 
			
		||||
                if (curl_multi_add_handle (multi, curl) == CURLM_OK)
 | 
			
		||||
                if (symbols->curl_multi_add_handle (multi, curl) == CURLM_OK)
 | 
			
		||||
                    return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -100,24 +189,25 @@ public:
 | 
			
		||||
    void cleanup()
 | 
			
		||||
    {
 | 
			
		||||
        const ScopedLock lock (cleanupLock);
 | 
			
		||||
        const ScopedLock sl (CURLSymbols::getLibcurlLock());
 | 
			
		||||
 | 
			
		||||
        if (curl != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            curl_multi_remove_handle (multi, curl);
 | 
			
		||||
            symbols->curl_multi_remove_handle (multi, curl);
 | 
			
		||||
 | 
			
		||||
            if (headerList != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                curl_slist_free_all (headerList);
 | 
			
		||||
                symbols->curl_slist_free_all (headerList);
 | 
			
		||||
                headerList = nullptr;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            curl_easy_cleanup (curl);
 | 
			
		||||
            symbols->curl_easy_cleanup (curl);
 | 
			
		||||
            curl = nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (multi != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            curl_multi_cleanup (multi);
 | 
			
		||||
            symbols->curl_multi_cleanup (multi);
 | 
			
		||||
            multi = nullptr;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -132,7 +222,7 @@ public:
 | 
			
		||||
    {
 | 
			
		||||
        auto address = url.toString (! isPost);
 | 
			
		||||
 | 
			
		||||
        curl_version_info_data* data = curl_version_info (CURLVERSION_NOW);
 | 
			
		||||
        curl_version_info_data* data = symbols->curl_version_info (CURLVERSION_NOW);
 | 
			
		||||
        jassert (data != nullptr);
 | 
			
		||||
 | 
			
		||||
        if (! requestHeaders.endsWithChar ('\n'))
 | 
			
		||||
@ -146,22 +236,22 @@ public:
 | 
			
		||||
 | 
			
		||||
        auto userAgent = String ("curl/") + data->version;
 | 
			
		||||
 | 
			
		||||
        if (curl_easy_setopt (curl, CURLOPT_URL, address.toRawUTF8()) == CURLE_OK
 | 
			
		||||
            && curl_easy_setopt (curl, CURLOPT_WRITEDATA, this) == CURLE_OK
 | 
			
		||||
            && curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, StaticCurlWrite) == CURLE_OK
 | 
			
		||||
            && curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1) == CURLE_OK
 | 
			
		||||
            && curl_easy_setopt (curl, CURLOPT_MAXREDIRS, static_cast<long> (maxRedirects)) == CURLE_OK
 | 
			
		||||
            && curl_easy_setopt (curl, CURLOPT_USERAGENT, userAgent.toRawUTF8()) == CURLE_OK
 | 
			
		||||
            && curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, (maxRedirects > 0 ? 1 : 0)) == CURLE_OK)
 | 
			
		||||
        if (symbols->curl_easy_setopt (curl, CURLOPT_URL, address.toRawUTF8()) == CURLE_OK
 | 
			
		||||
            && symbols->curl_easy_setopt (curl, CURLOPT_WRITEDATA, this) == CURLE_OK
 | 
			
		||||
            && symbols->curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, StaticCurlWrite) == CURLE_OK
 | 
			
		||||
            && symbols->curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1) == CURLE_OK
 | 
			
		||||
            && symbols->curl_easy_setopt (curl, CURLOPT_MAXREDIRS, static_cast<long> (maxRedirects)) == CURLE_OK
 | 
			
		||||
            && symbols->curl_easy_setopt (curl, CURLOPT_USERAGENT, userAgent.toRawUTF8()) == CURLE_OK
 | 
			
		||||
            && symbols->curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, (maxRedirects > 0 ? 1 : 0)) == CURLE_OK)
 | 
			
		||||
        {
 | 
			
		||||
            if (isPost)
 | 
			
		||||
            {
 | 
			
		||||
                if (curl_easy_setopt (curl, CURLOPT_READDATA, this) != CURLE_OK
 | 
			
		||||
                    || curl_easy_setopt (curl, CURLOPT_READFUNCTION, StaticCurlRead) != CURLE_OK)
 | 
			
		||||
                if (symbols->curl_easy_setopt (curl, CURLOPT_READDATA, this) != CURLE_OK
 | 
			
		||||
                    || symbols->curl_easy_setopt (curl, CURLOPT_READFUNCTION, StaticCurlRead) != CURLE_OK)
 | 
			
		||||
                    return false;
 | 
			
		||||
 | 
			
		||||
                if (curl_easy_setopt (curl, CURLOPT_POST, 1) != CURLE_OK
 | 
			
		||||
                    || curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t> (headersAndPostData.getSize())) != CURLE_OK)
 | 
			
		||||
                if (symbols->curl_easy_setopt (curl, CURLOPT_POST, 1) != CURLE_OK
 | 
			
		||||
                    || symbols->curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t> (headersAndPostData.getSize())) != CURLE_OK)
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -169,20 +259,20 @@ public:
 | 
			
		||||
            bool hasSpecialRequestCmd = isPost ? (httpRequest != "POST") : (httpRequest != "GET");
 | 
			
		||||
 | 
			
		||||
            if (hasSpecialRequestCmd)
 | 
			
		||||
                if (curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, httpRequest.toRawUTF8()) != CURLE_OK)
 | 
			
		||||
                if (symbols->curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, httpRequest.toRawUTF8()) != CURLE_OK)
 | 
			
		||||
                    return false;
 | 
			
		||||
 | 
			
		||||
            if (curl_easy_setopt (curl, CURLOPT_HEADERDATA, this) != CURLE_OK
 | 
			
		||||
                || curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, StaticCurlHeader) != CURLE_OK)
 | 
			
		||||
            if (symbols->curl_easy_setopt (curl, CURLOPT_HEADERDATA, this) != CURLE_OK
 | 
			
		||||
                || symbols->curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, StaticCurlHeader) != CURLE_OK)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if (timeOutMs > 0)
 | 
			
		||||
            {
 | 
			
		||||
                auto timeOutSecs = ((long) timeOutMs + 999) / 1000;
 | 
			
		||||
 | 
			
		||||
                if (curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, timeOutSecs) != CURLE_OK
 | 
			
		||||
                    || curl_easy_setopt (curl, CURLOPT_LOW_SPEED_LIMIT, 100) != CURLE_OK
 | 
			
		||||
                    || curl_easy_setopt (curl, CURLOPT_LOW_SPEED_TIME, timeOutSecs) != CURLE_OK)
 | 
			
		||||
                if (symbols->curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, timeOutSecs) != CURLE_OK
 | 
			
		||||
                    || symbols->curl_easy_setopt (curl, CURLOPT_LOW_SPEED_LIMIT, 100) != CURLE_OK
 | 
			
		||||
                    || symbols->curl_easy_setopt (curl, CURLOPT_LOW_SPEED_TIME, timeOutSecs) != CURLE_OK)
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -212,10 +302,10 @@ public:
 | 
			
		||||
 | 
			
		||||
                // fromLines will always return at least one line if the string is not empty
 | 
			
		||||
                jassert (headerLines.size() > 0);
 | 
			
		||||
                headerList = curl_slist_append (headerList, headerLines [0].toRawUTF8());
 | 
			
		||||
                headerList = symbols->curl_slist_append (headerList, headerLines [0].toRawUTF8());
 | 
			
		||||
 | 
			
		||||
                for (int i = 1; (i < headerLines.size() && headerList != nullptr); ++i)
 | 
			
		||||
                    headerList = curl_slist_append (headerList, headerLines [i].toRawUTF8());
 | 
			
		||||
                    headerList = symbols->curl_slist_append (headerList, headerLines [i].toRawUTF8());
 | 
			
		||||
 | 
			
		||||
                if (headerList == nullptr)
 | 
			
		||||
                {
 | 
			
		||||
@ -223,7 +313,7 @@ public:
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headerList) != CURLE_OK)
 | 
			
		||||
                if (symbols->curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headerList) != CURLE_OK)
 | 
			
		||||
                {
 | 
			
		||||
                    cleanup();
 | 
			
		||||
                    return false;
 | 
			
		||||
@ -272,12 +362,12 @@ public:
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            long responseCode;
 | 
			
		||||
            if (curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &responseCode) == CURLE_OK)
 | 
			
		||||
            if (symbols->curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &responseCode) == CURLE_OK)
 | 
			
		||||
                statusCode = static_cast<int> (responseCode);
 | 
			
		||||
 | 
			
		||||
            // get content length size
 | 
			
		||||
            double curlLength;
 | 
			
		||||
            if (curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &curlLength) == CURLE_OK)
 | 
			
		||||
            if (symbols->curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &curlLength) == CURLE_OK)
 | 
			
		||||
                contentLength = static_cast<int64> (curlLength);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -295,7 +385,7 @@ public:
 | 
			
		||||
        {
 | 
			
		||||
            int cnt = 0;
 | 
			
		||||
 | 
			
		||||
            if (CURLMsg* msg = curl_multi_info_read (multi, &cnt))
 | 
			
		||||
            if (CURLMsg* msg = symbols->curl_multi_info_read (multi, &cnt))
 | 
			
		||||
            {
 | 
			
		||||
                if (msg->msg == CURLMSG_DONE && msg->easy_handle == curl)
 | 
			
		||||
                {
 | 
			
		||||
@ -328,7 +418,7 @@ public:
 | 
			
		||||
            if (multi == nullptr)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if ((lastError = (int) curl_multi_timeout (multi, &curl_timeo)) != CURLM_OK)
 | 
			
		||||
            if ((lastError = (int) symbols->curl_multi_timeout (multi, &curl_timeo)) != CURLM_OK)
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -350,7 +440,7 @@ public:
 | 
			
		||||
            if (multi == nullptr)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if ((lastError = (int) curl_multi_fdset (multi, &fdread, &fdwrite, &fdexcep, &maxfd)) != CURLM_OK)
 | 
			
		||||
            if ((lastError = (int) symbols->curl_multi_fdset (multi, &fdread, &fdwrite, &fdexcep, &maxfd)) != CURLM_OK)
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -374,7 +464,7 @@ public:
 | 
			
		||||
        {
 | 
			
		||||
            const ScopedLock lock (cleanupLock);
 | 
			
		||||
 | 
			
		||||
            while ((curlRet = (int) curl_multi_perform (multi, &still_running)) == CURLM_CALL_MULTI_PERFORM)
 | 
			
		||||
            while ((curlRet = (int) symbols->curl_multi_perform (multi, &still_running)) == CURLM_CALL_MULTI_PERFORM)
 | 
			
		||||
            {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -510,6 +600,7 @@ public:
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    WebInputStream& owner;
 | 
			
		||||
    const URL url;
 | 
			
		||||
    std::unique_ptr<CURLSymbols> symbols { CURLSymbols::create() };
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    // curl stuff
 | 
			
		||||
 | 
			
		||||
@ -189,29 +189,27 @@ static bool isFileExecutable (const String& filename)
 | 
			
		||||
 | 
			
		||||
bool Process::openDocument (const String& fileName, const String& parameters)
 | 
			
		||||
{
 | 
			
		||||
    String cmdString (fileName.replace (" ", "\\ ",false));
 | 
			
		||||
    auto cmdString = fileName.replace (" ", "\\ ", false);
 | 
			
		||||
    cmdString << " " << parameters;
 | 
			
		||||
 | 
			
		||||
    if (/*URL::isProbablyAWebsiteURL (fileName)
 | 
			
		||||
          ||*/ cmdString.startsWithIgnoreCase ("file:")
 | 
			
		||||
         /*|| URL::isProbablyAnEmailAddress (fileName)*/
 | 
			
		||||
    if (cmdString.startsWithIgnoreCase ("file:")
 | 
			
		||||
         || File::createFileWithoutCheckingPath (fileName).isDirectory()
 | 
			
		||||
         || ! isFileExecutable (fileName))
 | 
			
		||||
    {
 | 
			
		||||
        // create a command that tries to launch a bunch of likely browsers
 | 
			
		||||
        static const char* const browserNames[] = { "xdg-open", "/etc/alternatives/x-www-browser", "firefox", "mozilla",
 | 
			
		||||
                                                    "google-chrome", "chromium-browser", "opera", "konqueror" };
 | 
			
		||||
        StringArray cmdLines;
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < numElementsInArray (browserNames); ++i)
 | 
			
		||||
            cmdLines.add (String (browserNames[i]) + " " + cmdString.trim().quoted());
 | 
			
		||||
        for (auto browserName : { "xdg-open", "/etc/alternatives/x-www-browser", "firefox", "mozilla",
 | 
			
		||||
                                  "google-chrome", "chromium-browser", "opera", "konqueror" })
 | 
			
		||||
        {
 | 
			
		||||
            cmdLines.add (String (browserName) + " " + cmdString.trim());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cmdString = cmdLines.joinIntoString (" || ");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const char* const argv[4] = { "/bin/sh", "-c", cmdString.toUTF8(), 0 };
 | 
			
		||||
 | 
			
		||||
    const int cpid = fork();
 | 
			
		||||
    auto cpid = fork();
 | 
			
		||||
 | 
			
		||||
    if (cpid == 0)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
@ -80,7 +80,7 @@ String SystemStats::getCpuModel()
 | 
			
		||||
    return getCpuInfo ("model name");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int SystemStats::getCpuSpeedInMegaherz()
 | 
			
		||||
int SystemStats::getCpuSpeedInMegahertz()
 | 
			
		||||
{
 | 
			
		||||
    return roundToInt (getCpuInfo ("cpu MHz").getFloatValue());
 | 
			
		||||
}
 | 
			
		||||
@ -142,16 +142,26 @@ String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getU
 | 
			
		||||
void CPUInformation::initialise() noexcept
 | 
			
		||||
{
 | 
			
		||||
    auto flags = getCpuInfo ("flags");
 | 
			
		||||
    hasMMX   = flags.contains ("mmx");
 | 
			
		||||
    hasSSE   = flags.contains ("sse");
 | 
			
		||||
    hasSSE2  = flags.contains ("sse2");
 | 
			
		||||
    hasSSE3  = flags.contains ("sse3");
 | 
			
		||||
    has3DNow = flags.contains ("3dnow");
 | 
			
		||||
    hasSSSE3 = flags.contains ("ssse3");
 | 
			
		||||
    hasSSE41 = flags.contains ("sse4_1");
 | 
			
		||||
    hasSSE42 = flags.contains ("sse4_2");
 | 
			
		||||
    hasAVX   = flags.contains ("avx");
 | 
			
		||||
    hasAVX2  = flags.contains ("avx2");
 | 
			
		||||
    hasMMX             = flags.contains ("mmx");
 | 
			
		||||
    hasSSE             = flags.contains ("sse");
 | 
			
		||||
    hasSSE2            = flags.contains ("sse2");
 | 
			
		||||
    hasSSE3            = flags.contains ("sse3");
 | 
			
		||||
    has3DNow           = flags.contains ("3dnow");
 | 
			
		||||
    hasSSSE3           = flags.contains ("ssse3");
 | 
			
		||||
    hasSSE41           = flags.contains ("sse4_1");
 | 
			
		||||
    hasSSE42           = flags.contains ("sse4_2");
 | 
			
		||||
    hasAVX             = flags.contains ("avx");
 | 
			
		||||
    hasAVX2            = flags.contains ("avx2");
 | 
			
		||||
    hasAVX512F         = flags.contains ("avx512f");
 | 
			
		||||
    hasAVX512BW        = flags.contains ("avx512bw");
 | 
			
		||||
    hasAVX512CD        = flags.contains ("avx512cd");
 | 
			
		||||
    hasAVX512DQ        = flags.contains ("avx512dq");
 | 
			
		||||
    hasAVX512ER        = flags.contains ("avx512er");
 | 
			
		||||
    hasAVX512IFMA      = flags.contains ("avx512ifma");
 | 
			
		||||
    hasAVX512PF        = flags.contains ("avx512pf");
 | 
			
		||||
    hasAVX512VBMI      = flags.contains ("avx512vbmi");
 | 
			
		||||
    hasAVX512VL        = flags.contains ("avx512vl");
 | 
			
		||||
    hasAVX512VPOPCNTDQ = flags.contains ("avx512_vpopcntdq");
 | 
			
		||||
 | 
			
		||||
    numLogicalCPUs  = getCpuInfo ("processor").getIntValue() + 1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,7 @@ namespace MacFileHelpers
 | 
			
		||||
        {
 | 
			
		||||
            const String type (buf.f_fstypename);
 | 
			
		||||
 | 
			
		||||
            while (*types != 0)
 | 
			
		||||
            while (*types != nullptr)
 | 
			
		||||
                if (type.equalsIgnoreCase (*types++))
 | 
			
		||||
                    return true;
 | 
			
		||||
        }
 | 
			
		||||
@ -105,14 +105,14 @@ namespace MacFileHelpers
 | 
			
		||||
   #else
 | 
			
		||||
    static bool launchExecutable (const String& pathAndArguments)
 | 
			
		||||
    {
 | 
			
		||||
        const char* const argv[4] = { "/bin/sh", "-c", pathAndArguments.toUTF8(), 0 };
 | 
			
		||||
 | 
			
		||||
        const int cpid = fork();
 | 
			
		||||
        auto cpid = fork();
 | 
			
		||||
 | 
			
		||||
        if (cpid == 0)
 | 
			
		||||
        {
 | 
			
		||||
            const char* const argv[4] = { "/bin/sh", "-c", pathAndArguments.toUTF8(), nullptr };
 | 
			
		||||
 | 
			
		||||
            // Child process
 | 
			
		||||
            if (execve (argv[0], (char**) argv, 0) < 0)
 | 
			
		||||
            if (execve (argv[0], (char**) argv, nullptr) < 0)
 | 
			
		||||
                exit (0);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
@ -412,10 +412,8 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String&
 | 
			
		||||
    JUCE_AUTORELEASEPOOL
 | 
			
		||||
    {
 | 
			
		||||
        NSString* fileNameAsNS (juceStringToNS (fileName));
 | 
			
		||||
        NSURL* filenameAsURL ([NSURL URLWithString: fileNameAsNS]);
 | 
			
		||||
 | 
			
		||||
        if (filenameAsURL == nil)
 | 
			
		||||
            filenameAsURL = [NSURL fileURLWithPath: fileNameAsNS];
 | 
			
		||||
        NSURL* filenameAsURL = File::createFileWithoutCheckingPath (fileName).exists() ? [NSURL fileURLWithPath: fileNameAsNS]
 | 
			
		||||
                                                                                       : [NSURL URLWithString: fileNameAsNS];
 | 
			
		||||
 | 
			
		||||
      #if JUCE_IOS
 | 
			
		||||
        ignoreUnused (parameters);
 | 
			
		||||
@ -442,11 +440,13 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String&
 | 
			
		||||
            StringArray params;
 | 
			
		||||
            params.addTokens (parameters, true);
 | 
			
		||||
 | 
			
		||||
            NSMutableArray* paramArray = [[[NSMutableArray alloc] init] autorelease];
 | 
			
		||||
            NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease];
 | 
			
		||||
 | 
			
		||||
            NSMutableArray* paramArray = [[NSMutableArray new] autorelease];
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < params.size(); ++i)
 | 
			
		||||
                [paramArray addObject: juceStringToNS (params[i])];
 | 
			
		||||
 | 
			
		||||
            NSMutableDictionary* dict = [[[NSMutableDictionary alloc] init] autorelease];
 | 
			
		||||
            [dict setObject: paramArray
 | 
			
		||||
                     forKey: nsStringLiteral ("NSWorkspaceLaunchConfigurationArguments")];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,12 +32,12 @@ void MACAddress::findAllAddresses (Array<MACAddress>& result)
 | 
			
		||||
        for (const ifaddrs* cursor = addrs; cursor != nullptr; cursor = cursor->ifa_next)
 | 
			
		||||
        {
 | 
			
		||||
            // Required to avoid misaligned pointer access
 | 
			
		||||
            sockaddr_storage sto;
 | 
			
		||||
            std::memcpy (&sto, cursor->ifa_addr, sizeof (sockaddr_storage));
 | 
			
		||||
            sockaddr sto;
 | 
			
		||||
            std::memcpy (&sto, cursor->ifa_addr, sizeof (sockaddr));
 | 
			
		||||
 | 
			
		||||
            if (sto.ss_family == AF_LINK)
 | 
			
		||||
            if (sto.sa_family == AF_LINK)
 | 
			
		||||
            {
 | 
			
		||||
                auto sadd = (const sockaddr_dl*) cursor->ifa_addr;
 | 
			
		||||
                auto sadd = reinterpret_cast<const sockaddr_dl*> (cursor->ifa_addr);
 | 
			
		||||
 | 
			
		||||
               #ifndef IFT_ETHER
 | 
			
		||||
                enum { IFT_ETHER = 6 };
 | 
			
		||||
@ -127,7 +127,7 @@ public:
 | 
			
		||||
        DelegateClass::setState (delegate, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~URLConnectionState()
 | 
			
		||||
    ~URLConnectionState() override
 | 
			
		||||
    {
 | 
			
		||||
        signalThreadShouldExit();
 | 
			
		||||
 | 
			
		||||
@ -331,7 +331,8 @@ public:
 | 
			
		||||
    NSMutableData* data = nil;
 | 
			
		||||
    NSDictionary* headers = nil;
 | 
			
		||||
    int statusCode = 0;
 | 
			
		||||
    bool initialised = false, hasFailed = false, hasFinished = false, isBeingDeleted = false;
 | 
			
		||||
    std::atomic<bool> initialised { false };
 | 
			
		||||
    bool hasFailed = false, hasFinished = false, isBeingDeleted = false;
 | 
			
		||||
    const int numRedirectsToFollow;
 | 
			
		||||
    int numRedirects = 0;
 | 
			
		||||
    int64 latestTotalBytes = 0;
 | 
			
		||||
@ -413,9 +414,10 @@ struct BackgroundDownloadTask  : public URL::DownloadTask
 | 
			
		||||
                            String extraHeadersToUse,
 | 
			
		||||
                            URL::DownloadTask::Listener* listenerToUse,
 | 
			
		||||
                            bool shouldUsePostRequest)
 | 
			
		||||
         : targetLocation (targetLocationToUse), listener (listenerToUse),
 | 
			
		||||
         : listener (listenerToUse),
 | 
			
		||||
           uniqueIdentifier (String (urlToUse.toString (true).hashCode64()) + String (Random().nextInt64()))
 | 
			
		||||
    {
 | 
			
		||||
        targetLocation = targetLocationToUse;
 | 
			
		||||
        downloaded = -1;
 | 
			
		||||
 | 
			
		||||
        static DelegateClass cls;
 | 
			
		||||
@ -488,7 +490,6 @@ struct BackgroundDownloadTask  : public URL::DownloadTask
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //==============================================================================
 | 
			
		||||
    File targetLocation;
 | 
			
		||||
    URL::DownloadTask::Listener* listener;
 | 
			
		||||
    NSObject<NSURLSessionDelegate>* delegate = nil;
 | 
			
		||||
    NSURLSession* session = nil;
 | 
			
		||||
@ -691,7 +692,7 @@ public:
 | 
			
		||||
        DelegateClass::setState (delegate, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~URLConnectionState()
 | 
			
		||||
    ~URLConnectionState() override
 | 
			
		||||
    {
 | 
			
		||||
        stop();
 | 
			
		||||
 | 
			
		||||
@ -808,7 +809,8 @@ public:
 | 
			
		||||
    {
 | 
			
		||||
        DBG (nsStringToJuce ([error description])); ignoreUnused (error);
 | 
			
		||||
        nsUrlErrorCode = [error code];
 | 
			
		||||
        hasFailed = initialised = true;
 | 
			
		||||
        hasFailed = true;
 | 
			
		||||
        initialised = true;
 | 
			
		||||
        signalThreadShouldExit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -826,7 +828,8 @@ public:
 | 
			
		||||
 | 
			
		||||
    void finishedLoading()
 | 
			
		||||
    {
 | 
			
		||||
        hasFinished = initialised = true;
 | 
			
		||||
        hasFinished = true;
 | 
			
		||||
        initialised = true;
 | 
			
		||||
        signalThreadShouldExit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -860,7 +863,7 @@ public:
 | 
			
		||||
    NSDictionary* headers = nil;
 | 
			
		||||
    NSInteger nsUrlErrorCode = 0;
 | 
			
		||||
    int statusCode = 0;
 | 
			
		||||
    bool initialised = false, hasFailed = false, hasFinished = false;
 | 
			
		||||
    std::atomic<bool> initialised { false }, hasFailed { false }, hasFinished { false };
 | 
			
		||||
    const int numRedirectsToFollow;
 | 
			
		||||
    int numRedirects = 0;
 | 
			
		||||
    int latestTotalBytes = 0;
 | 
			
		||||
@ -962,6 +965,9 @@ public:
 | 
			
		||||
            createConnection();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (connection == nullptr)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if (! connection->start (owner, webInputListener))
 | 
			
		||||
        {
 | 
			
		||||
            // Workaround for deployment targets below 10.10 where HTTPS POST requests with keep-alive fail with the NSURLErrorNetworkConnectionLost error code.
 | 
			
		||||
@ -977,7 +983,7 @@ public:
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (connection != nullptr && connection->headers != nil)
 | 
			
		||||
        if (connection->headers != nil)
 | 
			
		||||
        {
 | 
			
		||||
            statusCode = connection->statusCode;
 | 
			
		||||
 | 
			
		||||
@ -1093,38 +1099,44 @@ private:
 | 
			
		||||
    {
 | 
			
		||||
        jassert (connection == nullptr);
 | 
			
		||||
 | 
			
		||||
        if (NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (url.toString (! isPost))]
 | 
			
		||||
                                                               cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
 | 
			
		||||
                                                           timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)])
 | 
			
		||||
        if (NSURL* nsURL = [NSURL URLWithString: juceStringToNS (url.toString (! isPost))])
 | 
			
		||||
        {
 | 
			
		||||
            [req setHTTPMethod: [NSString stringWithUTF8String: httpRequestCmd.toRawUTF8()]];
 | 
			
		||||
 | 
			
		||||
            if (isPost)
 | 
			
		||||
            if (NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: nsURL
 | 
			
		||||
                                                                   cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
 | 
			
		||||
                                                               timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)])
 | 
			
		||||
            {
 | 
			
		||||
                WebInputStream::createHeadersAndPostData (url, headers, postData);
 | 
			
		||||
                if (NSString* httpMethod = [NSString stringWithUTF8String: httpRequestCmd.toRawUTF8()])
 | 
			
		||||
                {
 | 
			
		||||
                    [req setHTTPMethod: httpMethod];
 | 
			
		||||
 | 
			
		||||
                if (postData.getSize() > 0)
 | 
			
		||||
                    [req setHTTPBody: [NSData dataWithBytes: postData.getData()
 | 
			
		||||
                                                     length: postData.getSize()]];
 | 
			
		||||
                    if (isPost)
 | 
			
		||||
                    {
 | 
			
		||||
                        WebInputStream::createHeadersAndPostData (url, headers, postData);
 | 
			
		||||
 | 
			
		||||
                        if (postData.getSize() > 0)
 | 
			
		||||
                            [req setHTTPBody: [NSData dataWithBytes: postData.getData()
 | 
			
		||||
                                                             length: postData.getSize()]];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    StringArray headerLines;
 | 
			
		||||
                    headerLines.addLines (headers);
 | 
			
		||||
                    headerLines.removeEmptyStrings (true);
 | 
			
		||||
 | 
			
		||||
                    for (int i = 0; i < headerLines.size(); ++i)
 | 
			
		||||
                    {
 | 
			
		||||
                        auto key   = headerLines[i].upToFirstOccurrenceOf (":", false, false).trim();
 | 
			
		||||
                        auto value = headerLines[i].fromFirstOccurrenceOf (":", false, false).trim();
 | 
			
		||||
 | 
			
		||||
                        if (key.isNotEmpty() && value.isNotEmpty())
 | 
			
		||||
                            [req addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Workaround for an Apple bug. See https://github.com/AFNetworking/AFNetworking/issues/2334
 | 
			
		||||
                    [req HTTPBody];
 | 
			
		||||
 | 
			
		||||
                    connection.reset (new URLConnectionState (req, numRedirectsToFollow));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            StringArray headerLines;
 | 
			
		||||
            headerLines.addLines (headers);
 | 
			
		||||
            headerLines.removeEmptyStrings (true);
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < headerLines.size(); ++i)
 | 
			
		||||
            {
 | 
			
		||||
                String key   = headerLines[i].upToFirstOccurrenceOf (":", false, false).trim();
 | 
			
		||||
                String value = headerLines[i].fromFirstOccurrenceOf (":", false, false).trim();
 | 
			
		||||
 | 
			
		||||
                if (key.isNotEmpty() && value.isNotEmpty())
 | 
			
		||||
                    [req addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Workaround for an Apple bug. See https://github.com/AFNetworking/AFNetworking/issues/2334
 | 
			
		||||
            [req HTTPBody];
 | 
			
		||||
 | 
			
		||||
            connection.reset (new URLConnectionState (req, numRedirectsToFollow));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ namespace juce
 | 
			
		||||
 | 
			
		||||
String String::fromCFString (CFStringRef cfString)
 | 
			
		||||
{
 | 
			
		||||
    if (cfString == 0)
 | 
			
		||||
    if (cfString == nullptr)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    CFRange range = { 0, CFStringGetLength (cfString) };
 | 
			
		||||
@ -72,7 +72,7 @@ String String::convertToPrecomposedUnicode() const
 | 
			
		||||
 | 
			
		||||
    map.mappingVersion = kUnicodeUseLatestMapping;
 | 
			
		||||
 | 
			
		||||
    UnicodeToTextInfo conversionInfo = 0;
 | 
			
		||||
    UnicodeToTextInfo conversionInfo = {};
 | 
			
		||||
    String result;
 | 
			
		||||
 | 
			
		||||
    if (CreateUnicodeToTextInfo (&map, &conversionInfo) == noErr)
 | 
			
		||||
@ -88,11 +88,11 @@ String String::convertToPrecomposedUnicode() const
 | 
			
		||||
        if (ConvertFromUnicodeToText (conversionInfo,
 | 
			
		||||
                                      bytesNeeded, (ConstUniCharArrayPtr) toUTF16().getAddress(),
 | 
			
		||||
                                      kUnicodeDefaultDirectionMask,
 | 
			
		||||
                                      0, 0, 0, 0,
 | 
			
		||||
                                      0, {}, {}, {},
 | 
			
		||||
                                      bytesNeeded, &bytesRead,
 | 
			
		||||
                                      &outputBufferSize, tempOut) == noErr)
 | 
			
		||||
        {
 | 
			
		||||
            result = String (CharPointer_UTF16 ((CharPointer_UTF16::CharType*) tempOut.get()));
 | 
			
		||||
            result = String (CharPointer_UTF16 (reinterpret_cast<CharPointer_UTF16::CharType*> (tempOut.get())));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        DisposeUnicodeToTextInfo (&conversionInfo);
 | 
			
		||||
 | 
			
		||||
@ -51,14 +51,17 @@ namespace SystemStatsHelpers
 | 
			
		||||
    {
 | 
			
		||||
        uint32 la = a, lb = b, lc = c, ld = d;
 | 
			
		||||
 | 
			
		||||
        asm ("mov %%ebx, %%esi \n\t"
 | 
			
		||||
             "cpuid \n\t"
 | 
			
		||||
             "xchg %%esi, %%ebx"
 | 
			
		||||
               : "=a" (la), "=S" (lb), "=c" (lc), "=d" (ld) : "a" (type)
 | 
			
		||||
           #if JUCE_64BIT
 | 
			
		||||
                  , "b" (lb), "c" (lc), "d" (ld)
 | 
			
		||||
           #endif
 | 
			
		||||
        );
 | 
			
		||||
       #if JUCE_32BIT && defined (__pic__)
 | 
			
		||||
        asm ("mov %%ebx, %%edi\n"
 | 
			
		||||
             "cpuid\n"
 | 
			
		||||
             "xchg %%edi, %%ebx\n"
 | 
			
		||||
               : "=a" (la), "=D" (lb), "=c" (lc), "=d" (ld)
 | 
			
		||||
               : "a" (type), "c" (0));
 | 
			
		||||
       #else
 | 
			
		||||
        asm ("cpuid\n"
 | 
			
		||||
               : "=a" (la), "=b" (lb), "=c" (lc), "=d" (ld)
 | 
			
		||||
               : "a" (type), "c" (0));
 | 
			
		||||
       #endif
 | 
			
		||||
 | 
			
		||||
        a = la; b = lb; c = lc; d = ld;
 | 
			
		||||
    }
 | 
			
		||||
@ -83,7 +86,17 @@ void CPUInformation::initialise() noexcept
 | 
			
		||||
    hasAVX   = (c & (1u << 28)) != 0;
 | 
			
		||||
 | 
			
		||||
    SystemStatsHelpers::doCPUID (a, b, c, d, 7);
 | 
			
		||||
    hasAVX2  = (b & (1u <<  5)) != 0;
 | 
			
		||||
    hasAVX2            = (b & (1u <<  5)) != 0;
 | 
			
		||||
    hasAVX512F         = (b & (1u << 16)) != 0;
 | 
			
		||||
    hasAVX512DQ        = (b & (1u << 17)) != 0;
 | 
			
		||||
    hasAVX512IFMA      = (b & (1u << 21)) != 0;
 | 
			
		||||
    hasAVX512PF        = (b & (1u << 26)) != 0;
 | 
			
		||||
    hasAVX512ER        = (b & (1u << 27)) != 0;
 | 
			
		||||
    hasAVX512CD        = (b & (1u << 28)) != 0;
 | 
			
		||||
    hasAVX512BW        = (b & (1u << 30)) != 0;
 | 
			
		||||
    hasAVX512VL        = (b & (1u << 31)) != 0;
 | 
			
		||||
    hasAVX512VBMI      = (c & (1u <<  1)) != 0;
 | 
			
		||||
    hasAVX512VPOPCNTDQ = (c & (1u << 14)) != 0;
 | 
			
		||||
   #endif
 | 
			
		||||
 | 
			
		||||
    numLogicalCPUs = (int) [[NSProcessInfo processInfo] activeProcessorCount];
 | 
			
		||||
@ -140,17 +153,20 @@ String SystemStats::getOperatingSystemName()
 | 
			
		||||
String SystemStats::getDeviceDescription()
 | 
			
		||||
{
 | 
			
		||||
   #if JUCE_IOS
 | 
			
		||||
    return nsStringToJuce ([[UIDevice currentDevice] model]);
 | 
			
		||||
    const char* name = "hw.machine";
 | 
			
		||||
   #else
 | 
			
		||||
    const char* name = "hw.model";
 | 
			
		||||
   #endif
 | 
			
		||||
 | 
			
		||||
    size_t size;
 | 
			
		||||
    if (sysctlbyname ("hw.model", nullptr, &size, nullptr, 0) >= 0)
 | 
			
		||||
    if (sysctlbyname (name, nullptr, &size, nullptr, 0) >= 0)
 | 
			
		||||
    {
 | 
			
		||||
        HeapBlock<char> model (size);
 | 
			
		||||
        if (sysctlbyname ("hw.model", model,   &size, nullptr, 0) >= 0)
 | 
			
		||||
        if (sysctlbyname (name, model,   &size, nullptr, 0) >= 0)
 | 
			
		||||
            return model.get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
   #endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String SystemStats::getDeviceManufacturer()
 | 
			
		||||
@ -174,7 +190,7 @@ int SystemStats::getMemorySizeInMegabytes()
 | 
			
		||||
    uint64 mem = 0;
 | 
			
		||||
    size_t memSize = sizeof (mem);
 | 
			
		||||
    int mib[] = { CTL_HW, HW_MEMSIZE };
 | 
			
		||||
    sysctl (mib, 2, &mem, &memSize, 0, 0);
 | 
			
		||||
    sysctl (mib, 2, &mem, &memSize, nullptr, 0);
 | 
			
		||||
    return (int) (mem / (1024 * 1024));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -203,12 +219,12 @@ String SystemStats::getCpuModel()
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int SystemStats::getCpuSpeedInMegaherz()
 | 
			
		||||
int SystemStats::getCpuSpeedInMegahertz()
 | 
			
		||||
{
 | 
			
		||||
    uint64 speedHz = 0;
 | 
			
		||||
    size_t speedSize = sizeof (speedHz);
 | 
			
		||||
    int mib[] = { CTL_HW, HW_CPU_FREQ };
 | 
			
		||||
    sysctl (mib, 2, &speedHz, &speedSize, 0, 0);
 | 
			
		||||
    sysctl (mib, 2, &speedHz, &speedSize, nullptr, 0);
 | 
			
		||||
 | 
			
		||||
   #if JUCE_BIG_ENDIAN
 | 
			
		||||
    if (speedSize == 4)
 | 
			
		||||
 | 
			
		||||
@ -86,7 +86,7 @@ JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept
 | 
			
		||||
    struct kinfo_proc info;
 | 
			
		||||
    int m[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
 | 
			
		||||
    size_t sz = sizeof (info);
 | 
			
		||||
    sysctl (m, 4, &info, &sz, 0, 0);
 | 
			
		||||
    sysctl (m, 4, &info, &sz, nullptr, 0);
 | 
			
		||||
    return (info.kp_proc.p_flag & P_TRACED) != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -59,7 +59,7 @@ static inline NSURL* createNSURLFromFile (const File& f)
 | 
			
		||||
 | 
			
		||||
static inline NSArray* createNSArrayFromStringArray (const StringArray& strings)
 | 
			
		||||
{
 | 
			
		||||
    auto* array = [[NSMutableArray alloc] init];
 | 
			
		||||
    auto array = [[NSMutableArray alloc] init];
 | 
			
		||||
 | 
			
		||||
    for (auto string: strings)
 | 
			
		||||
        [array addObject:juceStringToNS (string)];
 | 
			
		||||
@ -71,7 +71,7 @@ static NSArray* varArrayToNSArray (const var& varToParse);
 | 
			
		||||
 | 
			
		||||
static NSDictionary* varObjectToNSDictionary (const var& varToParse)
 | 
			
		||||
{
 | 
			
		||||
    auto* dictionary = [NSMutableDictionary dictionary];
 | 
			
		||||
    auto dictionary = [NSMutableDictionary dictionary];
 | 
			
		||||
 | 
			
		||||
    if (varToParse.isObject())
 | 
			
		||||
    {
 | 
			
		||||
@ -118,7 +118,7 @@ static NSArray* varArrayToNSArray (const var& varToParse)
 | 
			
		||||
 | 
			
		||||
    const auto* varArray = varToParse.getArray();
 | 
			
		||||
 | 
			
		||||
    auto* array = [NSMutableArray arrayWithCapacity: (NSUInteger) varArray->size()];
 | 
			
		||||
    auto array = [NSMutableArray arrayWithCapacity: (NSUInteger) varArray->size()];
 | 
			
		||||
 | 
			
		||||
    for (const auto& aVar : *varArray)
 | 
			
		||||
    {
 | 
			
		||||
@ -145,29 +145,14 @@ static NSArray* varArrayToNSArray (const var& varToParse)
 | 
			
		||||
    return array;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static var nsArrayToVar (NSArray* array);
 | 
			
		||||
static var nsObjectToVar (NSObject* array);
 | 
			
		||||
 | 
			
		||||
static var nsDictionaryToVar (NSDictionary* dictionary)
 | 
			
		||||
{
 | 
			
		||||
    DynamicObject::Ptr dynamicObject = new DynamicObject();
 | 
			
		||||
    DynamicObject::Ptr dynamicObject (new DynamicObject());
 | 
			
		||||
 | 
			
		||||
    for (NSString* key in dictionary)
 | 
			
		||||
    {
 | 
			
		||||
        const auto keyString = nsStringToJuce (key);
 | 
			
		||||
 | 
			
		||||
        id value = dictionary[key];
 | 
			
		||||
 | 
			
		||||
        if ([value isKindOfClass: [NSString class]])
 | 
			
		||||
            dynamicObject->setProperty (keyString, nsStringToJuce ((NSString*) value));
 | 
			
		||||
        else if ([value isKindOfClass: [NSNumber class]])
 | 
			
		||||
            dynamicObject->setProperty (keyString, nsStringToJuce ([(NSNumber*) value stringValue]));
 | 
			
		||||
        else if ([value isKindOfClass: [NSDictionary class]])
 | 
			
		||||
            dynamicObject->setProperty (keyString, nsDictionaryToVar ((NSDictionary*) value));
 | 
			
		||||
        else if ([value isKindOfClass: [NSArray class]])
 | 
			
		||||
            dynamicObject->setProperty (keyString, nsArrayToVar ((NSArray*) value));
 | 
			
		||||
        else
 | 
			
		||||
            jassertfalse; // Unsupported yet, add here!
 | 
			
		||||
    }
 | 
			
		||||
        dynamicObject->setProperty (nsStringToJuce (key), nsObjectToVar (dictionary[key]));
 | 
			
		||||
 | 
			
		||||
    return var (dynamicObject.get());
 | 
			
		||||
}
 | 
			
		||||
@ -177,22 +162,26 @@ static var nsArrayToVar (NSArray* array)
 | 
			
		||||
    Array<var> resultArray;
 | 
			
		||||
 | 
			
		||||
    for (id value in array)
 | 
			
		||||
    {
 | 
			
		||||
        if ([value isKindOfClass: [NSString class]])
 | 
			
		||||
            resultArray.add (var (nsStringToJuce ((NSString*) value)));
 | 
			
		||||
        else if ([value isKindOfClass: [NSNumber class]])
 | 
			
		||||
            resultArray.add (var (nsStringToJuce ([(NSNumber*) value stringValue])));
 | 
			
		||||
        else if ([value isKindOfClass: [NSDictionary class]])
 | 
			
		||||
            resultArray.add (nsDictionaryToVar ((NSDictionary*) value));
 | 
			
		||||
        else if ([value isKindOfClass: [NSArray class]])
 | 
			
		||||
            resultArray.add (nsArrayToVar ((NSArray*) value));
 | 
			
		||||
        else
 | 
			
		||||
            jassertfalse; // Unsupported yet, add here!
 | 
			
		||||
    }
 | 
			
		||||
        resultArray.add (nsObjectToVar (value));
 | 
			
		||||
 | 
			
		||||
    return var (resultArray);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static var nsObjectToVar (NSObject* obj)
 | 
			
		||||
{
 | 
			
		||||
    if ([obj isKindOfClass: [NSString class]])          return nsStringToJuce ((NSString*) obj);
 | 
			
		||||
    else if ([obj isKindOfClass: [NSNumber class]])     return nsStringToJuce ([(NSNumber*) obj stringValue]);
 | 
			
		||||
    else if ([obj isKindOfClass: [NSDictionary class]]) return nsDictionaryToVar ((NSDictionary*) obj);
 | 
			
		||||
    else if ([obj isKindOfClass: [NSArray class]])      return nsArrayToVar ((NSArray*) obj);
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        // Unsupported yet, add here!
 | 
			
		||||
        jassertfalse;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if JUCE_MAC
 | 
			
		||||
template <typename RectangleType>
 | 
			
		||||
static NSRect makeNSRect (const RectangleType& r) noexcept
 | 
			
		||||
@ -211,7 +200,7 @@ static NSRect makeNSRect (const RectangleType& r) noexcept
 | 
			
		||||
template <typename ReturnValue, typename... Params>
 | 
			
		||||
static inline ReturnValue ObjCMsgSendSuper (struct objc_super* s, SEL sel, Params... params)
 | 
			
		||||
{
 | 
			
		||||
    typedef ReturnValue (*SuperFn)(struct objc_super*, SEL, Params...);
 | 
			
		||||
    using SuperFn = ReturnValue (*)(struct objc_super*, SEL, Params...);
 | 
			
		||||
    SuperFn fn = reinterpret_cast<SuperFn> (objc_msgSendSuper);
 | 
			
		||||
    return fn (s, sel, params...);
 | 
			
		||||
}
 | 
			
		||||
@ -226,16 +215,6 @@ static inline MsgSendFPRetFn getMsgSendFPRetFn() noexcept   { return (MsgSendFPR
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
template <typename ObjectType>
 | 
			
		||||
struct NSObjectRetainer
 | 
			
		||||
{
 | 
			
		||||
    inline NSObjectRetainer (ObjectType* o) : object (o)  { [object retain]; }
 | 
			
		||||
    inline ~NSObjectRetainer()                            { [object release]; }
 | 
			
		||||
 | 
			
		||||
    ObjectType* object;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
struct NSObjectDeleter
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										133
									
								
								modules/juce_core/native/juce_posix_IPAddress.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								modules/juce_core/native/juce_posix_IPAddress.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,133 @@
 | 
			
		||||
/*
 | 
			
		||||
  ==============================================================================
 | 
			
		||||
 | 
			
		||||
   This file is part of the JUCE library.
 | 
			
		||||
   Copyright (c) 2017 - ROLI Ltd.
 | 
			
		||||
 | 
			
		||||
   JUCE is an open source library subject to commercial or open-source
 | 
			
		||||
   licensing.
 | 
			
		||||
 | 
			
		||||
   The code included in this file is provided under the terms of the ISC license
 | 
			
		||||
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
 | 
			
		||||
   To use, copy, modify, and/or distribute this software for any purpose with or
 | 
			
		||||
   without fee is hereby granted provided that the above copyright notice and
 | 
			
		||||
   this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 | 
			
		||||
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 | 
			
		||||
   DISCLAIMED.
 | 
			
		||||
 | 
			
		||||
  ==============================================================================
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
namespace juce
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
namespace
 | 
			
		||||
{
 | 
			
		||||
    static IPAddress makeAddress (const sockaddr_in6* addr_in)
 | 
			
		||||
    {
 | 
			
		||||
        if (addr_in == nullptr)
 | 
			
		||||
            return {};
 | 
			
		||||
 | 
			
		||||
        auto addr = addr_in->sin6_addr;
 | 
			
		||||
 | 
			
		||||
        IPAddressByteUnion temp;
 | 
			
		||||
        uint16 arr[8];
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 8; ++i) // Swap bytes from network to host order
 | 
			
		||||
        {
 | 
			
		||||
            temp.split[0] = addr.s6_addr[i * 2 + 1];
 | 
			
		||||
            temp.split[1] = addr.s6_addr[i * 2];
 | 
			
		||||
 | 
			
		||||
            arr[i] = temp.combined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return IPAddress (arr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static IPAddress makeAddress (const sockaddr_in* addr_in)
 | 
			
		||||
    {
 | 
			
		||||
        if (addr_in->sin_addr.s_addr == INADDR_NONE)
 | 
			
		||||
            return {};
 | 
			
		||||
 | 
			
		||||
        return IPAddress (ntohl (addr_in->sin_addr.s_addr));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    struct InterfaceInfo
 | 
			
		||||
    {
 | 
			
		||||
        IPAddress interfaceAddress, broadcastAddress;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    bool operator== (const InterfaceInfo& lhs, const InterfaceInfo& rhs)
 | 
			
		||||
    {
 | 
			
		||||
        return lhs.interfaceAddress == rhs.interfaceAddress
 | 
			
		||||
            && lhs.broadcastAddress == rhs.broadcastAddress;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool populateInterfaceInfo (struct ifaddrs* ifa, InterfaceInfo& interfaceInfo)
 | 
			
		||||
    {
 | 
			
		||||
        if (ifa->ifa_addr != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            if (ifa->ifa_addr->sa_family == AF_INET)
 | 
			
		||||
            {
 | 
			
		||||
                auto interfaceAddressInfo = reinterpret_cast<sockaddr_in*> (ifa->ifa_addr);
 | 
			
		||||
                auto broadcastAddressInfo = reinterpret_cast<sockaddr_in*> (ifa->ifa_dstaddr);
 | 
			
		||||
 | 
			
		||||
                if (interfaceAddressInfo->sin_addr.s_addr != INADDR_NONE)
 | 
			
		||||
                {
 | 
			
		||||
                    interfaceInfo.interfaceAddress = makeAddress (interfaceAddressInfo);
 | 
			
		||||
                    interfaceInfo.broadcastAddress = makeAddress (broadcastAddressInfo);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if (ifa->ifa_addr->sa_family == AF_INET6)
 | 
			
		||||
            {
 | 
			
		||||
                interfaceInfo.interfaceAddress = makeAddress (reinterpret_cast<sockaddr_in6*> (ifa->ifa_addr));
 | 
			
		||||
                interfaceInfo.broadcastAddress = makeAddress (reinterpret_cast<sockaddr_in6*> (ifa->ifa_dstaddr));
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Array<InterfaceInfo> getAllInterfaceInfo()
 | 
			
		||||
    {
 | 
			
		||||
        Array<InterfaceInfo> interfaces;
 | 
			
		||||
        struct ifaddrs* ifaddr = nullptr;
 | 
			
		||||
 | 
			
		||||
        if (getifaddrs (&ifaddr) != -1)
 | 
			
		||||
        {
 | 
			
		||||
            for (auto* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
 | 
			
		||||
            {
 | 
			
		||||
                InterfaceInfo i;
 | 
			
		||||
 | 
			
		||||
                if (populateInterfaceInfo (ifa, i))
 | 
			
		||||
                    interfaces.addIfNotAlreadyThere (i);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            freeifaddrs (ifaddr);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return interfaces;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IPAddress::findAllAddresses (Array<IPAddress>& result, bool includeIPv6)
 | 
			
		||||
{
 | 
			
		||||
    for (auto& i : getAllInterfaceInfo())
 | 
			
		||||
        if (includeIPv6 || ! i.interfaceAddress.isIPv6)
 | 
			
		||||
            result.addIfNotAlreadyThere (i.interfaceAddress);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress& interfaceAddress)
 | 
			
		||||
{
 | 
			
		||||
    for (auto& i : getAllInterfaceInfo())
 | 
			
		||||
        if (i.interfaceAddress == interfaceAddress)
 | 
			
		||||
            return i.broadcastAddress;
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace juce
 | 
			
		||||
@ -47,18 +47,14 @@ public:
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool connect (int timeOutMilliseconds)
 | 
			
		||||
    {
 | 
			
		||||
        return openPipe (true, getTimeoutEnd (timeOutMilliseconds));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int read (char* destBuffer, int maxBytesToRead, int timeOutMilliseconds)
 | 
			
		||||
    {
 | 
			
		||||
        auto timeoutEnd = getTimeoutEnd (timeOutMilliseconds);
 | 
			
		||||
 | 
			
		||||
        if (pipeIn == -1)
 | 
			
		||||
        {
 | 
			
		||||
            pipeIn = openPipe (createdPipe ? pipeInName : pipeOutName, O_RDWR | O_NONBLOCK, timeoutEnd);
 | 
			
		||||
 | 
			
		||||
            if (pipeIn == -1)
 | 
			
		||||
                return -1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int bytesRead = 0;
 | 
			
		||||
 | 
			
		||||
        while (bytesRead < maxBytesToRead)
 | 
			
		||||
@ -89,13 +85,8 @@ public:
 | 
			
		||||
    {
 | 
			
		||||
        auto timeoutEnd = getTimeoutEnd (timeOutMilliseconds);
 | 
			
		||||
 | 
			
		||||
        if (pipeOut == -1)
 | 
			
		||||
        {
 | 
			
		||||
            pipeOut = openPipe (createdPipe ? pipeOutName : pipeInName, O_WRONLY, timeoutEnd);
 | 
			
		||||
 | 
			
		||||
            if (pipeOut == -1)
 | 
			
		||||
                return -1;
 | 
			
		||||
        }
 | 
			
		||||
        if (! openPipe (false, timeoutEnd))
 | 
			
		||||
            return -1;
 | 
			
		||||
 | 
			
		||||
        int bytesWritten = 0;
 | 
			
		||||
 | 
			
		||||
@ -160,6 +151,25 @@ private:
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool openPipe (bool isInput, uint32 timeoutEnd)
 | 
			
		||||
    {
 | 
			
		||||
        auto& pipe = isInput ? pipeIn : pipeOut;
 | 
			
		||||
        int flags = isInput ? O_RDWR | O_NONBLOCK : O_WRONLY;
 | 
			
		||||
 | 
			
		||||
        const String& pipeName = isInput ? (createdPipe ? pipeInName : pipeOutName)
 | 
			
		||||
                                         : (createdPipe ? pipeOutName : pipeInName);
 | 
			
		||||
 | 
			
		||||
        if (pipe == -1)
 | 
			
		||||
        {
 | 
			
		||||
            pipe = openPipe (pipeName, flags, timeoutEnd);
 | 
			
		||||
 | 
			
		||||
            if (pipe == -1)
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void waitForInput (int handle, int timeoutMsecs) noexcept
 | 
			
		||||
    {
 | 
			
		||||
        struct timeval timeout;
 | 
			
		||||
@ -170,7 +180,7 @@ private:
 | 
			
		||||
        FD_ZERO (&rset);
 | 
			
		||||
        FD_SET (handle, &rset);
 | 
			
		||||
 | 
			
		||||
        select (handle + 1, &rset, nullptr, 0, &timeout);
 | 
			
		||||
        select (handle + 1, &rset, nullptr, nullptr, &timeout);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
 | 
			
		||||
@ -211,6 +221,12 @@ bool NamedPipe::openInternal (const String& pipeName, bool createPipe, bool must
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (! pimpl->connect (200))
 | 
			
		||||
    {
 | 
			
		||||
        pimpl.reset();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,7 @@ void CriticalSection::exit() const noexcept         { pthread_mutex_unlock (&loc
 | 
			
		||||
WaitableEvent::WaitableEvent (bool useManualReset) noexcept
 | 
			
		||||
    : triggered (false), manualReset (useManualReset)
 | 
			
		||||
{
 | 
			
		||||
    pthread_cond_init (&condition, 0);
 | 
			
		||||
    pthread_cond_init (&condition, {});
 | 
			
		||||
 | 
			
		||||
    pthread_mutexattr_t atts;
 | 
			
		||||
    pthread_mutexattr_init (&atts);
 | 
			
		||||
@ -78,7 +78,7 @@ bool WaitableEvent::wait (int timeOutMillisecs) const noexcept
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            struct timeval now;
 | 
			
		||||
            gettimeofday (&now, 0);
 | 
			
		||||
            gettimeofday (&now, nullptr);
 | 
			
		||||
 | 
			
		||||
            struct timespec time;
 | 
			
		||||
            time.tv_sec  = now.tv_sec  + (timeOutMillisecs / 1000);
 | 
			
		||||
@ -428,11 +428,14 @@ bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64
 | 
			
		||||
 | 
			
		||||
bool File::deleteFile() const
 | 
			
		||||
{
 | 
			
		||||
    if (! exists() && ! isSymbolicLink())
 | 
			
		||||
        return true;
 | 
			
		||||
    if (! isSymbolicLink())
 | 
			
		||||
    {
 | 
			
		||||
        if (! exists())
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
    if (isDirectory())
 | 
			
		||||
        return rmdir (fullPath.toUTF8()) == 0;
 | 
			
		||||
        if (isDirectory())
 | 
			
		||||
            return rmdir (fullPath.toUTF8()) == 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return remove (fullPath.toUTF8()) == 0;
 | 
			
		||||
}
 | 
			
		||||
@ -466,7 +469,7 @@ Result File::createDirectoryInternal (const String& fileName) const
 | 
			
		||||
//==============================================================================
 | 
			
		||||
int64 juce_fileSetPosition (void* handle, int64 pos)
 | 
			
		||||
{
 | 
			
		||||
    if (handle != 0 && lseek (getFD (handle), (off_t) pos, SEEK_SET) == pos)
 | 
			
		||||
    if (handle != nullptr && lseek (getFD (handle), (off_t) pos, SEEK_SET) == pos)
 | 
			
		||||
        return pos;
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
@ -484,7 +487,7 @@ void FileInputStream::openHandle()
 | 
			
		||||
 | 
			
		||||
FileInputStream::~FileInputStream()
 | 
			
		||||
{
 | 
			
		||||
    if (fileHandle != 0)
 | 
			
		||||
    if (fileHandle != nullptr)
 | 
			
		||||
        close (getFD (fileHandle));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -492,7 +495,7 @@ size_t FileInputStream::readInternal (void* buffer, size_t numBytes)
 | 
			
		||||
{
 | 
			
		||||
    ssize_t result = 0;
 | 
			
		||||
 | 
			
		||||
    if (fileHandle != 0)
 | 
			
		||||
    if (fileHandle != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        result = ::read (getFD (fileHandle), buffer, numBytes);
 | 
			
		||||
 | 
			
		||||
@ -545,16 +548,16 @@ void FileOutputStream::openHandle()
 | 
			
		||||
 | 
			
		||||
void FileOutputStream::closeHandle()
 | 
			
		||||
{
 | 
			
		||||
    if (fileHandle != 0)
 | 
			
		||||
    if (fileHandle != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        close (getFD (fileHandle));
 | 
			
		||||
        fileHandle = 0;
 | 
			
		||||
        fileHandle = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ssize_t FileOutputStream::writeInternal (const void* data, size_t numBytes)
 | 
			
		||||
{
 | 
			
		||||
    if (fileHandle == 0)
 | 
			
		||||
    if (fileHandle == nullptr)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    auto result = ::write (getFD (fileHandle), data, numBytes);
 | 
			
		||||
@ -568,14 +571,14 @@ ssize_t FileOutputStream::writeInternal (const void* data, size_t numBytes)
 | 
			
		||||
#ifndef JUCE_ANDROID
 | 
			
		||||
void FileOutputStream::flushInternal()
 | 
			
		||||
{
 | 
			
		||||
    if (fileHandle != 0 && fsync (getFD (fileHandle)) == -1)
 | 
			
		||||
    if (fileHandle != nullptr && fsync (getFD (fileHandle)) == -1)
 | 
			
		||||
        status = getResultForErrno();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
Result FileOutputStream::truncate()
 | 
			
		||||
{
 | 
			
		||||
    if (fileHandle == 0)
 | 
			
		||||
    if (fileHandle == nullptr)
 | 
			
		||||
        return status;
 | 
			
		||||
 | 
			
		||||
    flush();
 | 
			
		||||
@ -607,10 +610,10 @@ void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exc
 | 
			
		||||
 | 
			
		||||
    if (fileHandle != -1)
 | 
			
		||||
    {
 | 
			
		||||
        void* m = mmap (0, (size_t) range.getLength(),
 | 
			
		||||
                        mode == readWrite ? (PROT_READ | PROT_WRITE) : PROT_READ,
 | 
			
		||||
                        exclusive ? MAP_PRIVATE : MAP_SHARED, fileHandle,
 | 
			
		||||
                        (off_t) range.getStart());
 | 
			
		||||
        auto m = mmap (nullptr, (size_t) range.getLength(),
 | 
			
		||||
                       mode == readWrite ? (PROT_READ | PROT_WRITE) : PROT_READ,
 | 
			
		||||
                       exclusive ? MAP_PRIVATE : MAP_SHARED, fileHandle,
 | 
			
		||||
                       (off_t) range.getStart());
 | 
			
		||||
 | 
			
		||||
        if (m != MAP_FAILED)
 | 
			
		||||
        {
 | 
			
		||||
@ -637,9 +640,6 @@ MemoryMappedFile::~MemoryMappedFile()
 | 
			
		||||
File juce_getExecutableFile();
 | 
			
		||||
File juce_getExecutableFile()
 | 
			
		||||
{
 | 
			
		||||
   #if JUCE_ANDROID
 | 
			
		||||
    return File (android.appFile);
 | 
			
		||||
   #else
 | 
			
		||||
    struct DLAddrReader
 | 
			
		||||
    {
 | 
			
		||||
        static String getFilename()
 | 
			
		||||
@ -654,7 +654,6 @@ File juce_getExecutableFile()
 | 
			
		||||
 | 
			
		||||
    static String filename = DLAddrReader::getFilename();
 | 
			
		||||
    return File::getCurrentWorkingDirectory().getChildFile (filename);
 | 
			
		||||
   #endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
@ -715,23 +714,7 @@ String File::getVolumeLabel() const
 | 
			
		||||
 | 
			
		||||
int File::getVolumeSerialNumber() const
 | 
			
		||||
{
 | 
			
		||||
    int result = 0;
 | 
			
		||||
/*    int fd = open (getFullPathName().toUTF8(), O_RDONLY | O_NONBLOCK);
 | 
			
		||||
 | 
			
		||||
    char info[512];
 | 
			
		||||
 | 
			
		||||
    #ifndef HDIO_GET_IDENTITY
 | 
			
		||||
     #define HDIO_GET_IDENTITY 0x030d
 | 
			
		||||
    #endif
 | 
			
		||||
 | 
			
		||||
    if (ioctl (fd, HDIO_GET_IDENTITY, info) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        DBG (String (info + 20, 20));
 | 
			
		||||
        result = String (info + 20, 20).trim().getIntValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    close (fd);*/
 | 
			
		||||
    return result;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
@ -907,21 +890,25 @@ extern JavaVM* androidJNIJavaVM;
 | 
			
		||||
extern "C" void* threadEntryProc (void*);
 | 
			
		||||
extern "C" void* threadEntryProc (void* userData)
 | 
			
		||||
{
 | 
			
		||||
   #if JUCE_ANDROID
 | 
			
		||||
    // JNI_OnLoad was not called - make sure you load the JUCE shared library
 | 
			
		||||
    // using System.load inside of Java
 | 
			
		||||
    jassert (androidJNIJavaVM != nullptr);
 | 
			
		||||
 | 
			
		||||
    JNIEnv* env;
 | 
			
		||||
    androidJNIJavaVM->AttachCurrentThread (&env, nullptr);
 | 
			
		||||
    setEnv (env);
 | 
			
		||||
   #endif
 | 
			
		||||
    auto* myself = static_cast<Thread*> (userData);
 | 
			
		||||
 | 
			
		||||
    JUCE_AUTORELEASEPOOL
 | 
			
		||||
    {
 | 
			
		||||
        juce_threadEntryPoint (userData);
 | 
			
		||||
        juce_threadEntryPoint (myself);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
   #if JUCE_ANDROID
 | 
			
		||||
    if (androidJNIJavaVM != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        void* env = nullptr;
 | 
			
		||||
        androidJNIJavaVM->GetEnv(&env, JNI_VERSION_1_2);
 | 
			
		||||
 | 
			
		||||
        // only detach if we have actually been attached
 | 
			
		||||
        if (env != nullptr)
 | 
			
		||||
            androidJNIJavaVM->DetachCurrentThread();
 | 
			
		||||
    }
 | 
			
		||||
   #endif
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -952,8 +939,8 @@ void Thread::launchThread()
 | 
			
		||||
    }
 | 
			
		||||
   #endif
 | 
			
		||||
 | 
			
		||||
    threadHandle = 0;
 | 
			
		||||
    pthread_t handle = 0;
 | 
			
		||||
    threadHandle = {};
 | 
			
		||||
    pthread_t handle = {};
 | 
			
		||||
    pthread_attr_t attr;
 | 
			
		||||
    pthread_attr_t* attrPtr = nullptr;
 | 
			
		||||
 | 
			
		||||
@ -963,6 +950,7 @@ void Thread::launchThread()
 | 
			
		||||
        pthread_attr_setstacksize (attrPtr, threadStackSize);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0)
 | 
			
		||||
    {
 | 
			
		||||
        pthread_detach (handle);
 | 
			
		||||
@ -976,13 +964,13 @@ void Thread::launchThread()
 | 
			
		||||
 | 
			
		||||
void Thread::closeThreadHandle()
 | 
			
		||||
{
 | 
			
		||||
    threadId = 0;
 | 
			
		||||
    threadHandle = 0;
 | 
			
		||||
    threadId = {};
 | 
			
		||||
    threadHandle = {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Thread::killThread()
 | 
			
		||||
{
 | 
			
		||||
    if (threadHandle.get() != 0)
 | 
			
		||||
    if (threadHandle.get() != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
       #if JUCE_ANDROID
 | 
			
		||||
        jassertfalse; // pthread_cancel not available!
 | 
			
		||||
@ -1312,7 +1300,7 @@ struct HighResolutionTimer::Pimpl
 | 
			
		||||
    {
 | 
			
		||||
        isRunning = false;
 | 
			
		||||
 | 
			
		||||
        if (thread == 0)
 | 
			
		||||
        if (thread == pthread_t())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (thread == pthread_self())
 | 
			
		||||
@ -1329,7 +1317,7 @@ struct HighResolutionTimer::Pimpl
 | 
			
		||||
        pthread_mutex_unlock (&timerMutex);
 | 
			
		||||
 | 
			
		||||
        pthread_join (thread, nullptr);
 | 
			
		||||
        thread = 0;
 | 
			
		||||
        thread = {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HighResolutionTimer& owner;
 | 
			
		||||
@ -1343,15 +1331,7 @@ private:
 | 
			
		||||
 | 
			
		||||
    static void* timerThread (void* param)
 | 
			
		||||
    {
 | 
			
		||||
       #if JUCE_ANDROID
 | 
			
		||||
        // JNI_OnLoad was not called - make sure you load the JUCE shared library
 | 
			
		||||
        // using System.load inside of Java
 | 
			
		||||
        jassert (androidJNIJavaVM != nullptr);
 | 
			
		||||
 | 
			
		||||
        JNIEnv* env;
 | 
			
		||||
        androidJNIJavaVM->AttachCurrentThread (&env, nullptr);
 | 
			
		||||
        setEnv (env);
 | 
			
		||||
       #else
 | 
			
		||||
       #if ! JUCE_ANDROID
 | 
			
		||||
        int dummy;
 | 
			
		||||
        pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy);
 | 
			
		||||
       #endif
 | 
			
		||||
 | 
			
		||||
@ -129,7 +129,7 @@ namespace WindowsFileHelpers
 | 
			
		||||
 | 
			
		||||
    File getSpecialFolderPath (int type)
 | 
			
		||||
    {
 | 
			
		||||
        WCHAR path [MAX_PATH + 256];
 | 
			
		||||
        WCHAR path[MAX_PATH + 256];
 | 
			
		||||
 | 
			
		||||
        if (SHGetSpecialFolderPath (0, path, type, FALSE))
 | 
			
		||||
            return File (String (path));
 | 
			
		||||
@ -139,7 +139,7 @@ namespace WindowsFileHelpers
 | 
			
		||||
 | 
			
		||||
    File getModuleFileName (HINSTANCE moduleHandle)
 | 
			
		||||
    {
 | 
			
		||||
        WCHAR dest [MAX_PATH + 256];
 | 
			
		||||
        WCHAR dest[MAX_PATH + 256];
 | 
			
		||||
        dest[0] = 0;
 | 
			
		||||
        GetModuleFileName (moduleHandle, dest, (DWORD) numElementsInArray (dest));
 | 
			
		||||
        return File (String (dest));
 | 
			
		||||
@ -147,7 +147,7 @@ namespace WindowsFileHelpers
 | 
			
		||||
 | 
			
		||||
    Result getResultForLastError()
 | 
			
		||||
    {
 | 
			
		||||
        TCHAR messageBuffer [256] = { 0 };
 | 
			
		||||
        TCHAR messageBuffer[256] = { 0 };
 | 
			
		||||
 | 
			
		||||
        FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
 | 
			
		||||
                       nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
 | 
			
		||||
@ -181,7 +181,7 @@ bool File::existsAsFile() const
 | 
			
		||||
 | 
			
		||||
bool File::isDirectory() const
 | 
			
		||||
{
 | 
			
		||||
    const DWORD attr = WindowsFileHelpers::getAtts (fullPath);
 | 
			
		||||
    auto attr = WindowsFileHelpers::getAtts (fullPath);
 | 
			
		||||
    return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 && attr != INVALID_FILE_ATTRIBUTES;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -250,22 +250,22 @@ bool File::moveToTrash() const
 | 
			
		||||
 | 
			
		||||
bool File::copyInternal (const File& dest) const
 | 
			
		||||
{
 | 
			
		||||
    return CopyFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer(), false) != 0;
 | 
			
		||||
    return CopyFile (fullPath.toWideCharPointer(),
 | 
			
		||||
                     dest.getFullPathName().toWideCharPointer(), false) != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool File::moveInternal (const File& dest) const
 | 
			
		||||
{
 | 
			
		||||
    return MoveFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer()) != 0;
 | 
			
		||||
    return MoveFile (fullPath.toWideCharPointer(),
 | 
			
		||||
                     dest.getFullPathName().toWideCharPointer()) != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool File::replaceInternal (const File& dest) const
 | 
			
		||||
{
 | 
			
		||||
    void* lpExclude = 0;
 | 
			
		||||
    void* lpReserved = 0;
 | 
			
		||||
 | 
			
		||||
    return ReplaceFile (dest.getFullPathName().toWideCharPointer(), fullPath.toWideCharPointer(),
 | 
			
		||||
                        0, REPLACEFILE_IGNORE_MERGE_ERRORS | REPLACEFILE_IGNORE_ACL_ERRORS,
 | 
			
		||||
                        lpExclude, lpReserved) != 0;
 | 
			
		||||
    return ReplaceFile (dest.getFullPathName().toWideCharPointer(),
 | 
			
		||||
                        fullPath.toWideCharPointer(),
 | 
			
		||||
                        0, REPLACEFILE_IGNORE_MERGE_ERRORS | 4 /*REPLACEFILE_IGNORE_ACL_ERRORS*/,
 | 
			
		||||
                        nullptr, nullptr) != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result File::createDirectoryInternal (const String& fileName) const
 | 
			
		||||
@ -279,14 +279,16 @@ int64 juce_fileSetPosition (void* handle, int64 pos)
 | 
			
		||||
{
 | 
			
		||||
    LARGE_INTEGER li;
 | 
			
		||||
    li.QuadPart = pos;
 | 
			
		||||
    li.LowPart = SetFilePointer ((HANDLE) handle, (LONG) li.LowPart, &li.HighPart, FILE_BEGIN);  // (returns -1 if it fails)
 | 
			
		||||
    li.LowPart = SetFilePointer ((HANDLE) handle, (LONG) li.LowPart,
 | 
			
		||||
                                 &li.HighPart, FILE_BEGIN);  // (returns -1 if it fails)
 | 
			
		||||
    return li.QuadPart;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileInputStream::openHandle()
 | 
			
		||||
{
 | 
			
		||||
    HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
 | 
			
		||||
                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
 | 
			
		||||
    auto h = CreateFile (file.getFullPathName().toWideCharPointer(),
 | 
			
		||||
                         GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
 | 
			
		||||
                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
 | 
			
		||||
 | 
			
		||||
    if (h != INVALID_HANDLE_VALUE)
 | 
			
		||||
        fileHandle = (void*) h;
 | 
			
		||||
@ -304,6 +306,7 @@ size_t FileInputStream::readInternal (void* buffer, size_t numBytes)
 | 
			
		||||
    if (fileHandle != 0)
 | 
			
		||||
    {
 | 
			
		||||
        DWORD actualNum = 0;
 | 
			
		||||
 | 
			
		||||
        if (! ReadFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, 0))
 | 
			
		||||
            status = WindowsFileHelpers::getResultForLastError();
 | 
			
		||||
 | 
			
		||||
@ -316,8 +319,9 @@ size_t FileInputStream::readInternal (void* buffer, size_t numBytes)
 | 
			
		||||
//==============================================================================
 | 
			
		||||
void FileOutputStream::openHandle()
 | 
			
		||||
{
 | 
			
		||||
    HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), GENERIC_WRITE, FILE_SHARE_READ, 0,
 | 
			
		||||
                           OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
 | 
			
		||||
    auto h = CreateFile (file.getFullPathName().toWideCharPointer(),
 | 
			
		||||
                         GENERIC_WRITE, FILE_SHARE_READ, 0,
 | 
			
		||||
                         OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
 | 
			
		||||
 | 
			
		||||
    if (h != INVALID_HANDLE_VALUE)
 | 
			
		||||
    {
 | 
			
		||||
@ -343,16 +347,13 @@ void FileOutputStream::closeHandle()
 | 
			
		||||
 | 
			
		||||
ssize_t FileOutputStream::writeInternal (const void* bufferToWrite, size_t numBytes)
 | 
			
		||||
{
 | 
			
		||||
    DWORD actualNum = 0;
 | 
			
		||||
 | 
			
		||||
    if (fileHandle != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        DWORD actualNum = 0;
 | 
			
		||||
        if (! WriteFile ((HANDLE) fileHandle, bufferToWrite, (DWORD) numBytes, &actualNum, 0))
 | 
			
		||||
            status = WindowsFileHelpers::getResultForLastError();
 | 
			
		||||
 | 
			
		||||
        return (ssize_t) actualNum;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
    return (ssize_t) actualNum;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileOutputStream::flushInternal()
 | 
			
		||||
@ -396,15 +397,17 @@ void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exc
 | 
			
		||||
        access = FILE_MAP_ALL_ACCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode,
 | 
			
		||||
                           exclusive ? 0 : (FILE_SHARE_READ | (mode == readWrite ? FILE_SHARE_WRITE : 0)), 0,
 | 
			
		||||
                           createType, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
 | 
			
		||||
    auto h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode,
 | 
			
		||||
                         exclusive ? 0 : (FILE_SHARE_READ | FILE_SHARE_DELETE | (mode == readWrite ? FILE_SHARE_WRITE : 0)), 0,
 | 
			
		||||
                         createType, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
 | 
			
		||||
 | 
			
		||||
    if (h != INVALID_HANDLE_VALUE)
 | 
			
		||||
    {
 | 
			
		||||
        fileHandle = (void*) h;
 | 
			
		||||
 | 
			
		||||
        HANDLE mappingHandle = CreateFileMapping (h, 0, protect, (DWORD) (range.getEnd() >> 32), (DWORD) range.getEnd(), 0);
 | 
			
		||||
        auto mappingHandle = CreateFileMapping (h, 0, protect,
 | 
			
		||||
                                                (DWORD) (range.getEnd() >> 32),
 | 
			
		||||
                                                (DWORD) range.getEnd(), 0);
 | 
			
		||||
 | 
			
		||||
        if (mappingHandle != 0)
 | 
			
		||||
        {
 | 
			
		||||
@ -461,8 +464,9 @@ bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64
 | 
			
		||||
    using namespace WindowsFileHelpers;
 | 
			
		||||
 | 
			
		||||
    bool ok = false;
 | 
			
		||||
    HANDLE h = CreateFile (fullPath.toWideCharPointer(), GENERIC_WRITE, FILE_SHARE_READ, 0,
 | 
			
		||||
                           OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
 | 
			
		||||
    auto h = CreateFile (fullPath.toWideCharPointer(),
 | 
			
		||||
                         GENERIC_WRITE, FILE_SHARE_READ, 0,
 | 
			
		||||
                         OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
 | 
			
		||||
 | 
			
		||||
    if (h != INVALID_HANDLE_VALUE)
 | 
			
		||||
    {
 | 
			
		||||
@ -482,7 +486,7 @@ bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64
 | 
			
		||||
//==============================================================================
 | 
			
		||||
void File::findFileSystemRoots (Array<File>& destArray)
 | 
			
		||||
{
 | 
			
		||||
    TCHAR buffer [2048] = { 0 };
 | 
			
		||||
    TCHAR buffer[2048] = { 0 };
 | 
			
		||||
    GetLogicalDriveStrings (2048, buffer);
 | 
			
		||||
 | 
			
		||||
    const TCHAR* n = buffer;
 | 
			
		||||
@ -499,13 +503,14 @@ void File::findFileSystemRoots (Array<File>& destArray)
 | 
			
		||||
    roots.sort (true);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < roots.size(); ++i)
 | 
			
		||||
        destArray.add (roots [i]);
 | 
			
		||||
        destArray.add (roots[i]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
String File::getVolumeLabel() const
 | 
			
		||||
{
 | 
			
		||||
    TCHAR dest[64];
 | 
			
		||||
 | 
			
		||||
    if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest,
 | 
			
		||||
                                (DWORD) numElementsInArray (dest), 0, 0, 0, 0, 0))
 | 
			
		||||
        dest[0] = 0;
 | 
			
		||||
@ -539,9 +544,9 @@ uint64 File::getFileIdentifier() const
 | 
			
		||||
{
 | 
			
		||||
    uint64 result = 0;
 | 
			
		||||
 | 
			
		||||
    HANDLE h = CreateFile (getFullPathName().toWideCharPointer(),
 | 
			
		||||
                           GENERIC_READ, FILE_SHARE_READ, nullptr,
 | 
			
		||||
                           OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
 | 
			
		||||
    auto h = CreateFile (getFullPathName().toWideCharPointer(),
 | 
			
		||||
                         GENERIC_READ, FILE_SHARE_READ, nullptr,
 | 
			
		||||
                         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
 | 
			
		||||
 | 
			
		||||
    if (h != INVALID_HANDLE_VALUE)
 | 
			
		||||
    {
 | 
			
		||||
@ -568,7 +573,7 @@ bool File::isOnHardDisk() const
 | 
			
		||||
    if (fullPath.isEmpty())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    const unsigned int n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
 | 
			
		||||
    auto n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
 | 
			
		||||
 | 
			
		||||
    if (fullPath.toLowerCase()[0] <= 'b' && fullPath[1] == ':')
 | 
			
		||||
        return n != DRIVE_REMOVABLE;
 | 
			
		||||
@ -583,7 +588,7 @@ bool File::isOnRemovableDrive() const
 | 
			
		||||
    if (fullPath.isEmpty())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    const unsigned int n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
 | 
			
		||||
    auto n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
 | 
			
		||||
 | 
			
		||||
    return n == DRIVE_CDROM
 | 
			
		||||
        || n == DRIVE_REMOTE
 | 
			
		||||
@ -612,7 +617,7 @@ File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type)
 | 
			
		||||
 | 
			
		||||
        case tempDirectory:
 | 
			
		||||
        {
 | 
			
		||||
            WCHAR dest [2048];
 | 
			
		||||
            WCHAR dest[2048];
 | 
			
		||||
            dest[0] = 0;
 | 
			
		||||
            GetTempPath ((DWORD) numElementsInArray (dest), dest);
 | 
			
		||||
            return File (String (dest));
 | 
			
		||||
@ -620,7 +625,7 @@ File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type)
 | 
			
		||||
 | 
			
		||||
        case windowsSystemDirectory:
 | 
			
		||||
        {
 | 
			
		||||
            WCHAR dest [2048];
 | 
			
		||||
            WCHAR dest[2048];
 | 
			
		||||
            dest[0] = 0;
 | 
			
		||||
            GetSystemDirectoryW (dest, (UINT) numElementsInArray (dest));
 | 
			
		||||
            return File (String (dest));
 | 
			
		||||
@ -645,7 +650,7 @@ File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type)
 | 
			
		||||
//==============================================================================
 | 
			
		||||
File File::getCurrentWorkingDirectory()
 | 
			
		||||
{
 | 
			
		||||
    WCHAR dest [MAX_PATH + 256];
 | 
			
		||||
    WCHAR dest[MAX_PATH + 256];
 | 
			
		||||
    dest[0] = 0;
 | 
			
		||||
    GetCurrentDirectory ((DWORD) numElementsInArray (dest), dest);
 | 
			
		||||
    return File (String (dest));
 | 
			
		||||
@ -710,7 +715,7 @@ static String readWindowsLnkFile (File lnkFile, bool wantsAbsolutePath)
 | 
			
		||||
             && (! wantsAbsolutePath || SUCCEEDED (shellLink->Resolve (0, SLR_ANY_MATCH | SLR_NO_UI))))
 | 
			
		||||
        {
 | 
			
		||||
            WIN32_FIND_DATA winFindData;
 | 
			
		||||
            WCHAR resolvedPath [MAX_PATH];
 | 
			
		||||
            WCHAR resolvedPath[MAX_PATH];
 | 
			
		||||
 | 
			
		||||
            DWORD flags = SLGP_UNCPRIORITY;
 | 
			
		||||
 | 
			
		||||
@ -1175,7 +1180,15 @@ bool NamedPipe::openInternal (const String& pipeName, const bool createPipe, boo
 | 
			
		||||
{
 | 
			
		||||
    pimpl.reset (new Pimpl (pipeName, createPipe, mustNotExist));
 | 
			
		||||
 | 
			
		||||
    if (createPipe && pimpl->pipeH == INVALID_HANDLE_VALUE)
 | 
			
		||||
    if (createPipe)
 | 
			
		||||
    {
 | 
			
		||||
        if (pimpl->pipeH == INVALID_HANDLE_VALUE)
 | 
			
		||||
        {
 | 
			
		||||
            pimpl.reset();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (! pimpl->connect (200))
 | 
			
		||||
    {
 | 
			
		||||
        pimpl.reset();
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
@ -343,6 +343,35 @@ private:
 | 
			
		||||
        InternetSetOption (sessionHandle, option, &timeOutMs, sizeof (timeOutMs));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void sendHTTPRequest (INTERNET_BUFFERS& buffers, WebInputStream::Listener* listener)
 | 
			
		||||
    {
 | 
			
		||||
        if (! HttpSendRequestEx (request, &buffers, 0, HSR_INITIATE, 0))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        int totalBytesSent = 0;
 | 
			
		||||
 | 
			
		||||
        while (totalBytesSent < (int) postData.getSize())
 | 
			
		||||
        {
 | 
			
		||||
            auto bytesToSend = jmin (1024, (int) postData.getSize() - totalBytesSent);
 | 
			
		||||
            DWORD bytesSent = 0;
 | 
			
		||||
 | 
			
		||||
            if (bytesToSend == 0
 | 
			
		||||
                || ! InternetWriteFile (request, static_cast<const char*> (postData.getData()) + totalBytesSent,
 | 
			
		||||
                                        (DWORD) bytesToSend, &bytesSent))
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            totalBytesSent += bytesSent;
 | 
			
		||||
 | 
			
		||||
            if (listener != nullptr
 | 
			
		||||
                && ! listener->postDataSendProgress (owner, totalBytesSent, (int) postData.getSize()))
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void openHTTPConnection (URL_COMPONENTS& uc, const String& address, WebInputStream::Listener* listener)
 | 
			
		||||
    {
 | 
			
		||||
        const TCHAR* mimeTypes[] = { _T("*/*"), nullptr };
 | 
			
		||||
@ -365,43 +394,29 @@ private:
 | 
			
		||||
        if (request != 0)
 | 
			
		||||
        {
 | 
			
		||||
            INTERNET_BUFFERS buffers = { 0 };
 | 
			
		||||
            buffers.dwStructSize = sizeof (INTERNET_BUFFERS);
 | 
			
		||||
            buffers.lpcszHeader = headers.toWideCharPointer();
 | 
			
		||||
            buffers.dwStructSize    = sizeof (INTERNET_BUFFERS);
 | 
			
		||||
            buffers.lpcszHeader     = headers.toWideCharPointer();
 | 
			
		||||
            buffers.dwHeadersLength = (DWORD) headers.length();
 | 
			
		||||
            buffers.dwBufferTotal = (DWORD) postData.getSize();
 | 
			
		||||
            buffers.dwBufferTotal   = (DWORD) postData.getSize();
 | 
			
		||||
 | 
			
		||||
            if (HttpSendRequestEx (request, &buffers, 0, HSR_INITIATE, 0))
 | 
			
		||||
            auto sendRequestAndTryEnd = [this, &buffers, &listener]() -> bool
 | 
			
		||||
            {
 | 
			
		||||
                int bytesSent = 0;
 | 
			
		||||
                sendHTTPRequest (buffers, listener);
 | 
			
		||||
 | 
			
		||||
                for (;;)
 | 
			
		||||
                {
 | 
			
		||||
                    const int bytesToDo = jmin (1024, (int) postData.getSize() - bytesSent);
 | 
			
		||||
                    DWORD bytesDone = 0;
 | 
			
		||||
                if (HttpEndRequest (request, 0, 0, 0))
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                    if (bytesToDo > 0
 | 
			
		||||
                         && ! InternetWriteFile (request,
 | 
			
		||||
                                                 static_cast<const char*> (postData.getData()) + bytesSent,
 | 
			
		||||
                                                 (DWORD) bytesToDo, &bytesDone))
 | 
			
		||||
                    {
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                return false;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
                    if (bytesToDo == 0 || (int) bytesDone < bytesToDo)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (HttpEndRequest (request, 0, 0, 0))
 | 
			
		||||
                            return;
 | 
			
		||||
            auto closed = sendRequestAndTryEnd();
 | 
			
		||||
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
            // N.B. this is needed for some authenticated HTTP connections
 | 
			
		||||
            if (! closed && GetLastError() == ERROR_INTERNET_FORCE_RETRY)
 | 
			
		||||
                closed = sendRequestAndTryEnd();
 | 
			
		||||
 | 
			
		||||
                    bytesSent += bytesDone;
 | 
			
		||||
 | 
			
		||||
                    if (listener != nullptr
 | 
			
		||||
                          && ! listener->postDataSendProgress (owner, bytesSent, (int) postData.getSize()))
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (closed)
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        closeConnection();
 | 
			
		||||
@ -514,6 +529,37 @@ namespace MACAddressHelpers
 | 
			
		||||
        split[1] = sa_in6->sin6_addr.u.Byte[off];
 | 
			
		||||
       #endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static IPAddress createAddress (const sockaddr_in6* sa_in6)
 | 
			
		||||
    {
 | 
			
		||||
        IPAddressByteUnion temp;
 | 
			
		||||
        uint16 arr[8];
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 8; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            split (sa_in6, i * 2, temp.split);
 | 
			
		||||
            arr[i] = temp.combined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return IPAddress (arr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static IPAddress createAddress (const sockaddr_in* sa_in)
 | 
			
		||||
    {
 | 
			
		||||
        return IPAddress ((uint8*) &sa_in->sin_addr.s_addr, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename Type>
 | 
			
		||||
    static void findAddresses (Array<IPAddress>& result, bool includeIPv6, Type start)
 | 
			
		||||
    {
 | 
			
		||||
        for (auto addr = start; addr != nullptr; addr = addr->Next)
 | 
			
		||||
        {
 | 
			
		||||
            if (addr->Address.lpSockaddr->sa_family == AF_INET)
 | 
			
		||||
                result.addIfNotAlreadyThere (createAddress ((sockaddr_in*) addr->Address.lpSockaddr));
 | 
			
		||||
            else if (addr->Address.lpSockaddr->sa_family == AF_INET6 && includeIPv6)
 | 
			
		||||
                result.addIfNotAlreadyThere (createAddress ((sockaddr_in6*) addr->Address.lpSockaddr));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MACAddress::findAllAddresses (Array<MACAddress>& result)
 | 
			
		||||
@ -530,94 +576,25 @@ void IPAddress::findAllAddresses (Array<IPAddress>& result, bool includeIPv6)
 | 
			
		||||
        result.addIfNotAlreadyThere (IPAddress::local (true));
 | 
			
		||||
 | 
			
		||||
    GetAdaptersAddressesHelper addressesHelper;
 | 
			
		||||
 | 
			
		||||
    if (addressesHelper.callGetAdaptersAddresses())
 | 
			
		||||
    {
 | 
			
		||||
        for (PIP_ADAPTER_ADDRESSES adapter = addressesHelper.adaptersAddresses; adapter != nullptr; adapter = adapter->Next)
 | 
			
		||||
        {
 | 
			
		||||
            PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr;
 | 
			
		||||
            for (pUnicast = adapter->FirstUnicastAddress; pUnicast != nullptr; pUnicast = pUnicast->Next)
 | 
			
		||||
            {
 | 
			
		||||
                if (pUnicast->Address.lpSockaddr->sa_family == AF_INET)
 | 
			
		||||
                {
 | 
			
		||||
                    const sockaddr_in* sa_in = (sockaddr_in*)pUnicast->Address.lpSockaddr;
 | 
			
		||||
                    IPAddress ip ((uint8*)&sa_in->sin_addr.s_addr, false);
 | 
			
		||||
                    result.addIfNotAlreadyThere (ip);
 | 
			
		||||
                }
 | 
			
		||||
                else if (pUnicast->Address.lpSockaddr->sa_family == AF_INET6 && includeIPv6)
 | 
			
		||||
                {
 | 
			
		||||
                    const sockaddr_in6* sa_in6 = (sockaddr_in6*)pUnicast->Address.lpSockaddr;
 | 
			
		||||
 | 
			
		||||
                    ByteUnion temp;
 | 
			
		||||
                    uint16 arr[8];
 | 
			
		||||
 | 
			
		||||
                    for (int i = 0; i < 8; ++i)
 | 
			
		||||
                    {
 | 
			
		||||
                        MACAddressHelpers::split (sa_in6, i * 2, temp.split);
 | 
			
		||||
                        arr[i] = temp.combined;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    IPAddress ip (arr);
 | 
			
		||||
                    result.addIfNotAlreadyThere (ip);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            PIP_ADAPTER_ANYCAST_ADDRESS   pAnycast = nullptr;
 | 
			
		||||
            for (pAnycast = adapter->FirstAnycastAddress; pAnycast != nullptr; pAnycast = pAnycast->Next)
 | 
			
		||||
            {
 | 
			
		||||
                if (pAnycast->Address.lpSockaddr->sa_family == AF_INET)
 | 
			
		||||
                {
 | 
			
		||||
                    const sockaddr_in* sa_in = (sockaddr_in*)pAnycast->Address.lpSockaddr;
 | 
			
		||||
                    IPAddress ip ((uint8*)&sa_in->sin_addr.s_addr, false);
 | 
			
		||||
                    result.addIfNotAlreadyThere (ip);
 | 
			
		||||
                }
 | 
			
		||||
                else if (pAnycast->Address.lpSockaddr->sa_family == AF_INET6 && includeIPv6)
 | 
			
		||||
                {
 | 
			
		||||
                    const sockaddr_in6* sa_in6 = (sockaddr_in6*)pAnycast->Address.lpSockaddr;
 | 
			
		||||
 | 
			
		||||
                    ByteUnion temp;
 | 
			
		||||
                    uint16 arr[8];
 | 
			
		||||
 | 
			
		||||
                    for (int i = 0; i < 8; ++i)
 | 
			
		||||
                    {
 | 
			
		||||
                        MACAddressHelpers::split (sa_in6, i * 2, temp.split);
 | 
			
		||||
                        arr[i] = temp.combined;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    IPAddress ip (arr);
 | 
			
		||||
                    result.addIfNotAlreadyThere (ip);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            PIP_ADAPTER_MULTICAST_ADDRESS pMulticast = nullptr;
 | 
			
		||||
            for (pMulticast = adapter->FirstMulticastAddress; pMulticast != nullptr; pMulticast = pMulticast->Next)
 | 
			
		||||
            {
 | 
			
		||||
                if (pMulticast->Address.lpSockaddr->sa_family == AF_INET)
 | 
			
		||||
                {
 | 
			
		||||
                    const sockaddr_in* sa_in = (sockaddr_in*)pMulticast->Address.lpSockaddr;
 | 
			
		||||
                    IPAddress ip ((uint8*)&sa_in->sin_addr.s_addr, false);
 | 
			
		||||
                    result.addIfNotAlreadyThere (ip);
 | 
			
		||||
                }
 | 
			
		||||
                else if (pMulticast->Address.lpSockaddr->sa_family == AF_INET6 && includeIPv6)
 | 
			
		||||
                {
 | 
			
		||||
                    const sockaddr_in6* sa_in6 = (sockaddr_in6*)pMulticast->Address.lpSockaddr;
 | 
			
		||||
 | 
			
		||||
                    ByteUnion temp;
 | 
			
		||||
                    uint16 arr[8];
 | 
			
		||||
 | 
			
		||||
                    for (int i = 0; i < 8; ++i)
 | 
			
		||||
                    {
 | 
			
		||||
                        MACAddressHelpers::split (sa_in6, i * 2, temp.split);
 | 
			
		||||
                        arr[i] = temp.combined;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    IPAddress ip (arr);
 | 
			
		||||
                    result.addIfNotAlreadyThere (ip);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            MACAddressHelpers::findAddresses (result, includeIPv6, adapter->FirstUnicastAddress);
 | 
			
		||||
            MACAddressHelpers::findAddresses (result, includeIPv6, adapter->FirstAnycastAddress);
 | 
			
		||||
            MACAddressHelpers::findAddresses (result, includeIPv6, adapter->FirstMulticastAddress);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress&)
 | 
			
		||||
{
 | 
			
		||||
    // TODO
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//==============================================================================
 | 
			
		||||
bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailAddress,
 | 
			
		||||
                                                      const String& emailSubject,
 | 
			
		||||
 | 
			
		||||
@ -151,7 +151,17 @@ void CPUInformation::initialise() noexcept
 | 
			
		||||
 | 
			
		||||
    callCPUID (info, 7);
 | 
			
		||||
 | 
			
		||||
    hasAVX2 = (info[1] & (1 << 5)) != 0;
 | 
			
		||||
    hasAVX2            = (info[1] & (1 << 5))   != 0;
 | 
			
		||||
    hasAVX512F         = (info[1] & (1u << 16)) != 0;
 | 
			
		||||
    hasAVX512DQ        = (info[1] & (1u << 17)) != 0;
 | 
			
		||||
    hasAVX512IFMA      = (info[1] & (1u << 21)) != 0;
 | 
			
		||||
    hasAVX512PF        = (info[1] & (1u << 26)) != 0;
 | 
			
		||||
    hasAVX512ER        = (info[1] & (1u << 27)) != 0;
 | 
			
		||||
    hasAVX512CD        = (info[1] & (1u << 28)) != 0;
 | 
			
		||||
    hasAVX512BW        = (info[1] & (1u << 30)) != 0;
 | 
			
		||||
    hasAVX512VL        = (info[1] & (1u << 31)) != 0;
 | 
			
		||||
    hasAVX512VBMI      = (info[2] & (1u <<  1)) != 0;
 | 
			
		||||
    hasAVX512VPOPCNTDQ = (info[2] & (1u << 14)) != 0;
 | 
			
		||||
 | 
			
		||||
    SYSTEM_INFO systemInfo;
 | 
			
		||||
    GetNativeSystemInfo (&systemInfo);
 | 
			
		||||
@ -288,8 +298,9 @@ int SystemStats::getMemorySizeInMegabytes()
 | 
			
		||||
//==============================================================================
 | 
			
		||||
String SystemStats::getEnvironmentVariable (const String& name, const String& defaultValue)
 | 
			
		||||
{
 | 
			
		||||
    DWORD len = GetEnvironmentVariableW (name.toWideCharPointer(), nullptr, 0);
 | 
			
		||||
    if (GetLastError() == ERROR_ENVVAR_NOT_FOUND)
 | 
			
		||||
    auto len = GetEnvironmentVariableW (name.toWideCharPointer(), nullptr, 0);
 | 
			
		||||
 | 
			
		||||
    if (len == 0)
 | 
			
		||||
        return String (defaultValue);
 | 
			
		||||
 | 
			
		||||
    HeapBlock<WCHAR> buffer (len);
 | 
			
		||||
@ -325,7 +336,7 @@ public:
 | 
			
		||||
       #endif
 | 
			
		||||
 | 
			
		||||
       #if JUCE_WIN32_TIMER_PERIOD > 0
 | 
			
		||||
        const MMRESULT res = timeBeginPeriod (JUCE_WIN32_TIMER_PERIOD);
 | 
			
		||||
        auto res = timeBeginPeriod (JUCE_WIN32_TIMER_PERIOD);
 | 
			
		||||
        ignoreUnused (res);
 | 
			
		||||
        jassert (res == TIMERR_NOERROR);
 | 
			
		||||
       #endif
 | 
			
		||||
@ -386,10 +397,10 @@ static int64 juce_getClockCycleCounter() noexcept
 | 
			
		||||
   #endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int SystemStats::getCpuSpeedInMegaherz()
 | 
			
		||||
int SystemStats::getCpuSpeedInMegahertz()
 | 
			
		||||
{
 | 
			
		||||
    const int64 cycles = juce_getClockCycleCounter();
 | 
			
		||||
    const uint32 millis = Time::getMillisecondCounter();
 | 
			
		||||
    auto cycles = juce_getClockCycleCounter();
 | 
			
		||||
    auto millis = Time::getMillisecondCounter();
 | 
			
		||||
    int lastResult = 0;
 | 
			
		||||
 | 
			
		||||
    for (;;)
 | 
			
		||||
@ -397,12 +408,12 @@ int SystemStats::getCpuSpeedInMegaherz()
 | 
			
		||||
        int n = 1000000;
 | 
			
		||||
        while (--n > 0) {}
 | 
			
		||||
 | 
			
		||||
        const uint32 millisElapsed = Time::getMillisecondCounter() - millis;
 | 
			
		||||
        const int64 cyclesNow = juce_getClockCycleCounter();
 | 
			
		||||
        auto millisElapsed = Time::getMillisecondCounter() - millis;
 | 
			
		||||
        auto cyclesNow = juce_getClockCycleCounter();
 | 
			
		||||
 | 
			
		||||
        if (millisElapsed > 80)
 | 
			
		||||
        {
 | 
			
		||||
            const int newResult = (int) (((cyclesNow - cycles) / millisElapsed) / 1000);
 | 
			
		||||
            auto newResult = (int) (((cyclesNow - cycles) / millisElapsed) / 1000);
 | 
			
		||||
 | 
			
		||||
            if (millisElapsed > 500 || (lastResult == newResult && newResult > 100))
 | 
			
		||||
                return newResult;
 | 
			
		||||
@ -431,7 +442,7 @@ bool Time::setSystemTimeToThisTime() const
 | 
			
		||||
    // first one sets it up, the second one kicks it in.
 | 
			
		||||
    // NB: the local variable is here to avoid analysers warning about having
 | 
			
		||||
    // two identical sub-expressions in the return statement
 | 
			
		||||
    bool firstCallToSetTimezone = SetLocalTime (&st) != 0;
 | 
			
		||||
    auto firstCallToSetTimezone = SetLocalTime (&st) != 0;
 | 
			
		||||
    return firstCallToSetTimezone && SetLocalTime (&st) != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -447,7 +458,7 @@ int SystemStats::getPageSize()
 | 
			
		||||
String SystemStats::getLogonName()
 | 
			
		||||
{
 | 
			
		||||
    TCHAR text [256] = { 0 };
 | 
			
		||||
    DWORD len = (DWORD) numElementsInArray (text) - 1;
 | 
			
		||||
    auto len = (DWORD) numElementsInArray (text) - 1;
 | 
			
		||||
    GetUserName (text, &len);
 | 
			
		||||
    return String (text, len);
 | 
			
		||||
}
 | 
			
		||||
@ -460,8 +471,8 @@ String SystemStats::getFullUserName()
 | 
			
		||||
String SystemStats::getComputerName()
 | 
			
		||||
{
 | 
			
		||||
    TCHAR text[128] = { 0 };
 | 
			
		||||
    DWORD len = (DWORD) numElementsInArray (text) - 1;
 | 
			
		||||
    GetComputerName (text, &len);
 | 
			
		||||
    auto len = (DWORD) numElementsInArray (text) - 1;
 | 
			
		||||
    GetComputerNameEx (ComputerNamePhysicalDnsHostname, text, &len);
 | 
			
		||||
    return String (text, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -485,10 +496,10 @@ String SystemStats::getDisplayLanguage()
 | 
			
		||||
    if (getUserDefaultUILanguage == nullptr)
 | 
			
		||||
        return "en";
 | 
			
		||||
 | 
			
		||||
    const DWORD langID = MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT);
 | 
			
		||||
    auto langID = MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT);
 | 
			
		||||
 | 
			
		||||
    String mainLang (getLocaleValue (langID, LOCALE_SISO639LANGNAME, "en"));
 | 
			
		||||
    String region   (getLocaleValue (langID, LOCALE_SISO3166CTRYNAME, nullptr));
 | 
			
		||||
    auto mainLang = getLocaleValue (langID, LOCALE_SISO639LANGNAME, "en");
 | 
			
		||||
    auto region   = getLocaleValue (langID, LOCALE_SISO3166CTRYNAME, nullptr);
 | 
			
		||||
 | 
			
		||||
    if (region.isNotEmpty())
 | 
			
		||||
        mainLang << '-' << region;
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user