fr.bmartel.android.fadecandy.service.FadecandyService.java Source code

Java tutorial

Introduction

Here is the source code for fr.bmartel.android.fadecandy.service.FadecandyService.java

Source

/**
 * The MIT License (MIT)
 * <p/>
 * Copyright (c) 2016 Bertrand Martel
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package fr.bmartel.android.fadecandy.service;

import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import fr.bmartel.android.fadecandy.activity.UsbEventReceiverActivity;
import fr.bmartel.android.fadecandy.constant.Constants;
import fr.bmartel.android.fadecandy.inter.IUsbEventListener;
import fr.bmartel.android.fadecandy.model.FadecandyColor;
import fr.bmartel.android.fadecandy.model.FadecandyConfig;
import fr.bmartel.android.fadecandy.model.FadecandyDevice;
import fr.bmartel.android.fadecandy.model.ServiceType;
import fr.bmartel.android.fadecandy.model.UsbItem;
import fr.bmartel.android.fadecandy.utils.ManualResetEvent;
import fr.bmartel.android.fadecandy.utils.NotificationHelper;

/**
 * Fadecandy Server service.
 *
 * @author Bertrand Martel
 */
public class FadecandyService extends Service {

    private final static String TAG = FadecandyService.class.getSimpleName();

    /**
     * broadcast action received when user accept USB permission.
     */
    private static final String ACTION_USB_PERMISSION = "fr.bmartel.fadecandy.USB_PERMISSION";

    /**
     * broadcast action received when user click to remove service notification.
     */
    public static final String ACTION_EXIT = "exit";

    /**
     * Usb manager instance.
     */
    private UsbManager mUsbManager;

    /**
     * List of Fadecandy USB device attached.
     */
    private HashMap<Integer, UsbItem> mUsbDevices = new HashMap<>();

    /**
     * Fadecandy server configuration.
     */
    private FadecandyConfig mConfig;

    /**
     * Service type persistent or non persistent.
     */
    private ServiceType mServiceType;

    /**
     * Server address.
     */
    private String mServerAddress;

    /**
     * Server port.
     */
    private int mServerPort;

    /**
     * Start Fadecandy server.
     *
     * @param config Fadecandy configuration in Json string.
     * @return start status (0 : OK | 1 : ERROR)
     */
    public native int startFcServer(String config);

    /**
     * Stop fadecandy server (async)
     */
    public native void stopFcServer();

    /**
     * notify Fadecandy server that a Fadecandy USB device has been attached and provide all its characteristics.
     *
     * @param vendorId       Fadecandy device vendor ID
     * @param productId      Fadecandy device product ID
     * @param serialNumber   Fadecandy device serial number (depend on Android version)
     * @param fileDescriptor USB file descriptor (what is used to identify the USB device)
     */
    public native void usbDeviceArrived(int vendorId, int productId, String serialNumber, int fileDescriptor);

    /**
     * notify Fadecandy server that a Fadecandy USB device has been detached.
     *
     * @param fileDescriptor
     */
    public native void usbDeviceLeft(int fileDescriptor);

    /**
     * define that server is started or not.
     */
    private boolean mIsServerRunning;

    /**
     * max time to wait for server close callback (in millis)
     */
    private final static int STOP_SERVER_TIMEOUT = 400;

    /**
     * list of USB listeners.
     */
    private List<IUsbEventListener> mUsbListeners = new ArrayList<>();

    /**
     * monitoring object used to wait for server close before starting server if already started.
     */
    private ManualResetEvent eventManager = new ManualResetEvent(false);

    /**
     * Shared preferences used by Service.
     */
    private SharedPreferences prefs;

    /**
     * define if user has click on notification to close Service.
     */
    private boolean mExit = false;

    /**
     * load shared libraries.
     */
    static {
        System.loadLibrary("websockets");
        System.loadLibrary("fadecandy-server");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.v(TAG, "onCreate()");

        mExit = false;

        mBinder = new FadecandyServiceBinder(this);

        prefs = this.getSharedPreferences(Constants.PREFERENCE_PREFS, Context.MODE_PRIVATE);
        String configStr = prefs.getString(Constants.PREFERENCE_CONFIG, getDefaultConfig().toJsonString());
        int serviceType = prefs.getInt(Constants.PREFERENCE_SERVICE_TYPE,
                ServiceType.getState(Constants.DEFAULT_SERVICE_TYPE));
        mServerPort = prefs.getInt(Constants.PREFERENCE_PORT, Constants.DEFAULT_SERVER_PORT);
        mServerAddress = prefs.getString(Constants.PREFERENCE_IP_ADDRESS, Constants.DEFAULT_SERVER_ADDRESS);

        mServiceType = ServiceType.getServiceType(serviceType);

        try {
            mConfig = new FadecandyConfig(new JSONObject(configStr));
        } catch (JSONException e) {
            mConfig = getDefaultConfig();
            e.printStackTrace();
        }

        mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

        initBroadcastReceiver();
    }

    /**
     * Initialize broadcast receiver to receive USB ATTACHED/DETACHED events.
     */
    private void initBroadcastReceiver() {

        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_USB_PERMISSION);
        filter.addAction(UsbEventReceiverActivity.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        filter.addAction(FadecandyService.ACTION_EXIT);
        registerReceiver(receiver, filter);
    }

    /**
     * add an usb listener to listen for Fadecandy device attached  / detached.
     *
     * @param listener
     */
    public void addUsbListener(IUsbEventListener listener) {
        mUsbListeners.add(listener);
    }

    /**
     * remove an usb listener.
     *
     * @param listener
     */
    public void removeUsbListener(IUsbEventListener listener) {
        mUsbListeners.remove(listener);
    }

    /**
     * initialize usb device list to request permission if not already given for already connected USB devices.
     */
    private void initUsbDeviceList() {

        mUsbDevices.clear();

        HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();

        Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

        while (deviceIterator.hasNext()) {
            UsbDevice device = deviceIterator.next();

            if (device.getVendorId() == Constants.FC_VENDOR && device.getProductId() == Constants.FC_PRODUCT) {
                dispatchAttached(device);
            }
        }
    }

    /**
     * start Fadecandy server with a new configuration.
     */
    public void startServer(FadecandyConfig config) {
        mConfig = config;
        startServer();
    }

    /**
     * stop server & unregister receiver.
     */
    public void clean() {
        stopFcServer();

        if (receiver != null) {
            unregisterReceiver(receiver);
        }
    }

    @Override
    public void onDestroy() {

        Log.v(TAG, "onDestroy");
        clean();

        super.onDestroy();

        //@todo find why a process still exist at this moment
        if (mExit) {
            android.os.Process.killProcess(android.os.Process.myPid());
        }
    }

    /**
     * get current Fadecandy server configuration.
     *
     * @return
     */
    public FadecandyConfig getConfig() {
        return mConfig;
    }

    /**
     * get default Fadecandy server configuration.
     *
     * @return
     */
    private FadecandyConfig getDefaultConfig() {

        List<Float> defaultGamma = new ArrayList<>();
        defaultGamma.add(1.0f);
        defaultGamma.add(1.0f);
        defaultGamma.add(1.0f);

        List<FadecandyDevice> fcDevices = new ArrayList<>();

        List<List<Integer>> map = new ArrayList<>();
        List<Integer> mapItem = new ArrayList<>();
        mapItem.add(0);
        mapItem.add(0);
        mapItem.add(0);
        mapItem.add(512);
        map.add(mapItem);

        fcDevices.add(new FadecandyDevice(map, "fadecandy"));

        return new FadecandyConfig(Constants.DEFAULT_SERVER_ADDRESS, Constants.DEFAULT_SERVER_PORT,
                new FadecandyColor(defaultGamma, 2.5f), true, fcDevices);
    }

    /**
     * dispatched attached usb device to fadecandy server native interface.
     *
     * @param device Usb device to be dispatched.
     */
    private void dispatchAttached(UsbDevice device) {

        if (!mUsbManager.hasPermission(device)) {

            Log.v(TAG, "Requesting permissiom to device");
            PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION),
                    0);
            mUsbManager.requestPermission(device, mPermissionIntent);

        } else {

            Log.v(TAG, "Already has permission : opening device");

            UsbItem item = openDevice(device);

            int fd = item.getConnection().getFileDescriptor();

            dispatchUsb(item, fd);
        }

    }

    /**
     * Broadcast receiver used to receive ATTACHED/DETACHED usb event & usb user permission events .
     */
    private final BroadcastReceiver receiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {

            String action = intent.getAction();

            if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                Log.v(TAG, "ACTION_USB_DEVICE_DETACHED");

                UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                if (device != null) {
                    Iterator it = mUsbDevices.entrySet().iterator();
                    while (it.hasNext()) {
                        Map.Entry<Integer, UsbItem> pair = (Map.Entry) it.next();
                        if (pair.getValue().getDevice().equals(device)) {
                            usbDeviceLeft(pair.getKey());
                            mUsbDevices.remove(pair.getKey());
                            for (int i = 0; i < mUsbListeners.size(); i++) {
                                mUsbListeners.get(i).onUsbDeviceDetached(pair.getValue());
                            }
                            break;
                        }
                    }

                } else {
                    Log.e(TAG, "usb device null");
                }

            } else if (UsbEventReceiverActivity.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                Log.v(TAG, "ACTION_USB_DEVICE_ATTACHED");

                UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                dispatchAttached(device);

            } else if (ACTION_USB_PERMISSION.equals(action)) {

                Log.v(TAG, "ACTION_USB_PERMISSION");

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {

                    Log.v(TAG, "Received permission result");

                    UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                    if (device != null) {

                        UsbItem item = openDevice(device);

                        if (item != null) {
                            int fd = item.getConnection().getFileDescriptor();
                            dispatchUsb(item, fd);
                        }
                    } else {
                        Log.v(TAG, "permission denied for device " + device);
                    }
                } else {
                    Log.e(TAG, "permission denied");
                }
            } else if (FadecandyService.ACTION_EXIT.equals(action)) {
                Log.v(TAG, "stopping FadecandyService");

                mExit = true;
                stopForeground(true);
                stopSelf();
            }
        }
    };

    /**
     * append USB device to usb list & pass to native interface
     *
     * @param device USB device object
     * @param fd     USB device file descriptor
     */
    private void dispatchUsb(UsbItem device, int fd) {

        String serialNum = "";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            serialNum = device.getDevice().getSerialNumber();
        } else {
            serialNum = "" + device.getDevice().getProductId();
        }

        mUsbDevices.put(fd, device);

        for (int i = 0; i < mUsbListeners.size(); i++) {
            mUsbListeners.get(i).onUsbDeviceAttached(device);
        }

        usbDeviceArrived(device.getDevice().getVendorId(), device.getDevice().getProductId(), serialNum, fd);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private FadecandyServiceBinder mBinder;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        //this is the activity which will be opened when user click on notification
        Intent testIntent = new Intent();
        if (intent != null && intent.hasExtra(Constants.SERVICE_EXTRA_ACTIVITY)) {
            testIntent.setComponent(
                    ComponentName.unflattenFromString(intent.getStringExtra(Constants.SERVICE_EXTRA_ACTIVITY)));
        }

        ServiceType serviceTypeOverride;

        if (intent != null && intent.hasExtra(Constants.SERVICE_EXTRA_SERVICE_TYPE)) {
            serviceTypeOverride = ServiceType.getServiceType(intent.getIntExtra(
                    Constants.SERVICE_EXTRA_SERVICE_TYPE, ServiceType.NON_PERSISTENT_SERVICE.ordinal()));
        } else {
            serviceTypeOverride = mServiceType;
        }

        switch (serviceTypeOverride) {
        case NON_PERSISTENT_SERVICE:
            Log.v(TAG, "starting NON_PERSISTENT_SERVICE...");
            return START_NOT_STICKY;
        case PERSISTENT_SERVICE:
            startForeground(4242, NotificationHelper.createNotification(this, null, testIntent));
            Log.v(TAG, "starting PERSISTENT_SERVICE...");
            return START_STICKY;
        }

        return START_NOT_STICKY;
    }

    /**
     * start Fadecandy server
     *
     * @return server status (0 : started | 1 : not stated (error))
     */
    public int startServer() {

        if (isServerRunning()) {

            eventManager.reset();

            stopServer();

            try {
                eventManager.waitOne(STOP_SERVER_TIMEOUT);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        mConfig = new FadecandyConfig(mServerAddress, mServerPort, mConfig.getFcColor(), mConfig.isVerbose(),
                mConfig.getFcDevices());

        int status = startFcServer(mConfig.toJsonString());

        initUsbDeviceList();

        if (status == 0) {
            Log.v(TAG, "server running");
            mIsServerRunning = true;
        } else {
            Log.e(TAG, "error occured while starting server");
        }

        return status;
    }

    /**
     * Stop Fadecandy server.
     *
     * @return stop status
     */
    public int stopServer() {
        Log.v(TAG, "stop server");
        stopFcServer();
        mIsServerRunning = false;
        return 0;
    }

    /**
     * define if server is started or not.
     *
     * @return
     */
    public boolean isServerRunning() {
        return mIsServerRunning;
    }

    /**
     * called from native to notify server has been closed successfully.
     */
    private void onServerClose() {
        eventManager.set();
    }

    /**
     * called from native to process USB transfer.
     *
     * @param fileDescriptor USB device file descriptor.
     * @param timeout        transfer timeout.
     * @param data           data to be transferred.
     * @return transfer status
     */
    private int bulkTransfer(int fileDescriptor, int timeout, byte[] data) {

        if (mUsbDevices.containsKey(fileDescriptor)) {
            if (mUsbDevices.get(fileDescriptor).getConnection() != null) {
                return mUsbDevices.get(fileDescriptor).getConnection()
                        .bulkTransfer(mUsbDevices.get(fileDescriptor).getUsbEndpoint(), data, data.length, timeout);
            }
        } else {
            Log.e(TAG, "device with fd " + fileDescriptor + " not found");
        }
        return -1;
    }

    /**
     * Open USB device & choose USBEndpoint.
     *
     * @param device Usb device object.
     * @return Ubs item composed of USBDevice , USBEndpoint & UsbConnection
     */
    private UsbItem openDevice(UsbDevice device) {

        UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

        try {
            UsbDeviceConnection connection = manager.openDevice(device);

            Log.v("USB", "Device name=" + device.getDeviceName());

            UsbInterface intf = null;

            for (int interfaceIndex = 0; interfaceIndex < device.getInterfaceCount(); interfaceIndex++) {

                UsbInterface usbInterface = device.getInterface(interfaceIndex);

                if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC) {
                    intf = usbInterface;
                }
            }

            if (intf == null) {
                intf = device.getInterface(0);
            }

            UsbEndpoint endpoint = intf.getEndpoint(0);

            if (connection != null) {
                connection.claimInterface(intf, true);
            } else {
                Log.e(TAG, "USB connection error");
                return null;
            }

            return new UsbItem(device, connection, endpoint);

        } catch (IllegalArgumentException e) {
        }
        return null;
    }

    /**
     * Retrieve service type (persistent or non persistent).
     *
     * @return
     */
    public ServiceType getServiceType() {
        return mServiceType;
    }

    /**
     * Set service type.
     *
     * @param serviceType
     */
    public void setServiceType(ServiceType serviceType) {
        this.mServiceType = serviceType;
        prefs.edit().putInt(Constants.PREFERENCE_SERVICE_TYPE, ServiceType.getState(serviceType)).apply();
    }

    /**
     * Retrieve server port.
     *
     * @return
     */
    public int getServerPort() {
        return mServerPort;
    }

    /**
     * Retrieve server addresss.
     *
     * @return
     */
    public String getIpAddress() {
        return mServerAddress;
    }

    /**
     * Set server port.
     *
     * @param port
     */
    public void setServerPort(int port) {
        this.mServerPort = port;
        prefs.edit().putInt(Constants.PREFERENCE_PORT, port).apply();
    }

    /**
     * Set server address.
     *
     * @param ip
     */
    public void setServerAddress(String ip) {
        this.mServerAddress = ip;
        prefs.edit().putString(Constants.PREFERENCE_IP_ADDRESS, ip).apply();
    }

    /**
     * Retrieve list of Fadecandy USB device attached.
     *
     * @return
     */
    public HashMap<Integer, UsbItem> getUsbDeviceMap() {
        return mUsbDevices;
    }
}