FileDocCategorySizeDatePackage
DeviceEmul.javaAPI DocphoneME MR2 API (J2ME)18266Wed May 02 18:00:30 BST 2007com.sun.midp.jsr82emul

DeviceEmul.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.midp.jsr82emul;

import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.util.Vector;

import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.DiscoveryAgent;

import com.sun.kvem.jsr082.bluetooth.DiscoveryAgentImpl;
import com.sun.midp.io.BluetoothUrl;
import com.sun.midp.main.Configuration;
import com.sun.midp.log.Logging;
import com.sun.kvem.jsr082.bluetooth.BCC;
import com.sun.midp.jsr082.BluetoothUtils;

/**
 * Emulates a Bluetooth device.
 */
public class DeviceEmul extends EmulationClient
        implements EmulUnit {
    /**
     * Represents and perfoms inquiry.
     * Constructing an instance starts inquiry in a new thread.
     */
    private class Inquiry implements Runnable {
        /** Access code parameter for the inquiry. */
        int accessCode;

        /** Indicates if this inquiry has been cancelled. */
        private boolean cancelled = false;

        /** Inquiry thread. */
        private Thread thread;

        /**
         * Constructs an instance and starts corresponding inquiry thread.
         * @param accessCode access code to be used within the inqiry
         */
        Inquiry(int accessCode) {
            this.accessCode = accessCode;
            thread = new Thread(this);
            thread.start();
        }

        /** Cancells current inquiry. */
        synchronized void cancel() {
            // not interrupting the corresponding thread to let it finish
            // emulation server communications
            cancelled = true;
        }

        /**
         * Implements <code>run()</code> of <code>Runnable</code> running
         * the inquiry
         */
        public void run() {
            try {
                InquiryResults inquiryResults;

                synchronized (serverTransaction) {
                    messenger.sendInt(toServer, Messenger.START_INQUIRY,
                        accessCode);

                    // let other threads work while the inquire is processed by
                    // emulation server
                    Thread.yield();

                    messenger.receive(fromServer);

                    if (messenger.getCode() != Messenger.INQUIRY_COMPLETED) {
                        throw new EmulationException();
                    }

                    inquiryResults = new InquiryResults(
                        messenger.getBytes());
                }

                byte[][] addresses = inquiryResults.getAddresses();
                int[] classes = inquiryResults.getClasses();

                for (int i = 0; i < addresses.length; i++) {
                    if (CheckCancelAndReportDiscovery(
                            addresses[i], classes[i])) {
                        break;
                    }
                }

                if (!cancelled) {
                    inquiryCompleted(true);
                }

            } catch (Throwable e) {
                inquiryCompleted(false);
             }

        }

        /**
         * Checks if inquiry cancelled and reports on device discovery to
         * listener if it is not.
         *
         * @param btaddr BluetoothAddress of device discovered
         * @param cod class of device discovered
         * @return <code>true</code> if inquiry is cancelled,
         *         <code>false</code> otherwise
         */
        private synchronized boolean CheckCancelAndReportDiscovery(
                byte[] btaddr, int cod) {

            if (!cancelled) {
                deviceDiscovered(btaddr, cod);
            }
            return cancelled;
        }
    }

    /** Initial access code. */
    private static int DEFAULT_AC = DiscoveryAgent.GIAC;
    
    /** Keeps current inquiry if any. */
    private Inquiry curInquiry = null;

    /** Bluetooth address. */
    byte[] address = null;
    
    /** Device state i.e. discoverable mode and device class. */
    private DeviceState deviceState = null;

    /** Lock for emulation server communications that require response. */
    private Object serverTransaction = new Object();

    /** Device emulation for the local device. */
    private static DeviceEmul localDeviceEmul = null;

    /**
     * Constructs an emulation instance and retrieves addres for it from
     *        emulation server.
     */
    public DeviceEmul() {
        try {
            connect();

            messenger.sendBytes(toServer, Messenger.REGISTER_DEVICE, 
                    getLocalIpBytes());
            messenger.receive(fromServer);

            if (messenger.getCode() != Messenger.REGISTERED) {
                throw new IOException("Error communicating emulation server");
            }
            
            address = messenger.getBytes();
            int cod = initDevice(address, DEFAULT_AC);
            deviceState = new DeviceState(cod, DEFAULT_AC);
            updateState();
            
            Log.log("DeviceEmul: my address is " +
                    BluetoothUtils.getAddressString(address));
        } catch (IOException e) {
            throw new EmulationException(
                "Error initializing local device emulation");
        }
    }
    
    /** 
     * Initializes local device parameters in shared emulation storage.
     * @param addr Bluetoth address bytes retrieved form emulation server
     * @param ac initial access code
     * @return initial class of device with service classes that are possibly
     *         saved after previous usage of device with the same address
     */
    private native int initDevice(byte[] addr, int ac);

    /**
     * Returns instance of this class for local device emulation.
     *
     * @return the device emulation object for the local device
     */
    public static synchronized DeviceEmul getLocalDeviceEmul() {
        if (localDeviceEmul == null) {
            localDeviceEmul = new DeviceEmul();
        }
        return localDeviceEmul;
    }

    /**
     * Retrieves IP address.
     * @return host computer IP address as byte array.
     */
    private byte[] getLocalIpBytes() {
        String ip = EmulationClient.getLocalIP();
        byte[] res = null;
        
        if (ip != null && ip.length() > 0) {
            int from = 0;
            int to = 0;
            byte[] parsed = new byte[4];
            
            try {
                for (int i = 0; i < 4; i++) {
                    to = ip.indexOf('.', from);
                    if (to < 0) {
                        to = ip.length();
                    }
                    parsed[i] = (byte)Integer.parseInt(ip.substring(from, to));
                    from = to + 1;
                }
                
                res = parsed;
            } catch (NumberFormatException e) {
                // res == null idenitifies retrieving failure
            }
        }
        
        if (res == null) {
            res = new byte[] {127, 0, 0, 1};
        }
        return res;
    }

    /**
     * Returns address of this device.
     * @return Bluetooth address
     */
    public byte[] getAddress() {
        return address;
    }

    /**
     * Registers service at emulation server.
     * @param serviceData combined service connection info
     * @throws IOException if connection to emulation server failed.
     */
    void registerService(ServiceConnectionData serviceData)
            throws IOException {

        synchronized (serverTransaction) {
            messenger.sendBytes(toServer, Messenger.REGISTER_SERVICE,
                serviceData.toByteArray(ServiceConnectionData.SERVER_DATA));
        }
    }

    /**
     * Unregisters service at emulation server.
     * @param serverSocketPort socket port desired service
     *        accepted connections at
     */
    void unregisterService(int serverSocketPort) {
        try {
            messenger.sendInt(toServer, Messenger.UNREGISTER_SERVICE,
                serverSocketPort);
        } catch (IOException e) {
            if (Logging.TRACE_ENABLED) {
                Logging.trace(e, "Unregistering service failed");
            }
        }
    }

    /** Sends device state update to emulation server. */
    private void updateState() {
        try {
            messenger.sendInt(toServer,
                Messenger.UPDATE_DEVICE_STATE, deviceState.toInt());
        } catch (IOException e) {
            throw new EmulationException(e.getMessage());
        }
    }

    /**
     * Starts inquiry.
     * @param accessCode access code of desired devices
     */
    private synchronized void startInquiry(int accessCode) {
        curInquiry = new Inquiry(accessCode);
    }

    /**
     * Cancels current inquiry.
     */
    private synchronized void cancelInquiry() {
        if (curInquiry != null) {
            curInquiry.cancel();
        }
    }
    
    /** 
     * Notifies on device discovery. 
     * @param addr Bluetooth address of device discovered
     * @param cod class of device discovered
     */
    private native void deviceDiscovered(byte[] addr, int cod);
    /** 
     * Notifies on inquiry completion.
     * @param success true if completed successfully, false otherwize
     */
    private native void inquiryCompleted(boolean success);

    /** Request code for updating service classes. */
    static final int UPDATE_CLASS = 0;
    /** Request code for updating access code. */
    static final int UPDATE_ACCESS = 1;
    /** Request code for updating starting inquiry. */
    static final int START_INQUIRY = 2;
    /** Request code for cancelling inquiry. */
    static final int CANCEL_INQUIRY = 3;
    /** Request code for initing devie. */
    static final int INIT_DEVICE = 4;
    
    /**
     * Processes the request.
     * @param request Packeted request
     */
    public void process(BytePack request) {
        switch (request.extract()) {
        case UPDATE_CLASS:
            Log.log("Processing UPDATE_CLASS");
            deviceState.setServiceClasses(request.extractInt());
            updateState();
            break;
        case UPDATE_ACCESS:
            Log.log("Processing UPDATE_ACCESS");
            deviceState.setDiscoverable(request.extractInt());
            updateState();
            break;
        case START_INQUIRY:
            Log.log("Processing START_INQUIRY");
            startInquiry(request.extractInt());
            break;
        case CANCEL_INQUIRY:
            Log.log("Processing CANCEL_INQUIRY");
            cancelInquiry();
            break;
        case INIT_DEVICE:
            Log.log("Processing INIT_DEVICE");
            // Nothing to do: the request has already caused 
            // construction of all the objects required.
            break;
        }
    }
    
    /** 
     * Saves device information in persistent storage for future use. 
     */
    private native void finalize();
}

/**
 * Utility class that allows packing inquiry results in bytes array.
 */
class InquiryResults {
    /** Amount of devices discovered. */
    int count = 0;

    /** Size of Bluetooth address byte representation. */
    private static final int ADDRESS_SIZE = Const.BTADDR_SIZE;
    /** Size of device class byte representation. */
    private static final int COD_SIZE = 3;
    /** Size of one result. */
    private static final int RESULT_SIZE = ADDRESS_SIZE + COD_SIZE;

    /** Addresses of discovered devices if not <code>null</code>. */
    private byte[][] addresses;
    /** Classes of discovered devices if not <code>null</code>. */
    private int[] classes = null;

    /** Inquiry results: Bluetooth address, device class pairs. */
    Vector results;

    /** Constructs an instance for filling up. */
    InquiryResults() {
        results = new Vector();
    }

    /**
     * Constructs an instance by byte representation and unpacks it to
     * normal addresses and device classes.
     *
     * @param data byte representation of inquiry results.
     */
    InquiryResults(byte[] data) {
        if (data == null || (data.length % RESULT_SIZE) != 0) {
            throw new IllegalArgumentException();
        }

        count = data.length / RESULT_SIZE;
        addresses = new byte[count][ADDRESS_SIZE];
        classes = new int[count];

        for (int i = 0; i < count; i++) {
            int j = i * RESULT_SIZE;

            classes[i] = (data[j++] & 0xff) | ((data[j++] & 0xff) << 8) |
                        ((data[j++] & 0xff) << 16);

            System.arraycopy(data, j, addresses[i], 0, ADDRESS_SIZE);
        }
    }

    /**
     * Returns classes of discovered devices.
     * @return int array that contains classes of discovered devices
     */
    int[] getClasses() {
        if (classes == null) {
            throw new IllegalArgumentException();
        }
        return classes;
    }

    /**
     * Returns addresses of discovered devices
     * @return array of bluetooth addresses, byte representation.
     */
    byte[][] getAddresses() {
        if (addresses == null) {
            throw new IllegalArgumentException();
        }
        return addresses;
    }

    /**
     * Adds new device discovered to results.
     * @param btaddr Bluetooth address of device discovered
     * @param cod class of device discovered
     */
    void add(byte[] btaddr, int cod) {
        byte[] bytes = new byte[RESULT_SIZE];

        bytes[0] = (byte)(cod & 0xff);
        bytes[1] = (byte)((cod >> 8) & 0xff);
        bytes[2] = (byte)((cod >> 16) & 0xff);

        System.arraycopy(btaddr, 0,
                bytes, COD_SIZE, ADDRESS_SIZE);

        results.addElement(bytes);
    }

    /**
     * Packs results to byte representation.
     * @return byte array that represent current results
     */
    byte[] toByteArray() {
        count = results.size();
        byte[] data = new byte[count * RESULT_SIZE];

        for (int i = 0; i < count; i++) {
            System.arraycopy((byte[])results.elementAt(i), 0,
                data, i * RESULT_SIZE, RESULT_SIZE);
        }

        return data;
    }
}

/**
 * Utility class that allows packing device class and discoverable mode into
 * single integer.
 */
class DeviceState {
    /** Packed information. */
    private int data = 0;
    
    /** Mask for highlighting device class. */
    private static final int DEVICE_CLASS = 0x1ffc;
    /** Mask for highlighting device class. */
    private static final int SERV_CLASSES = 0xffe000;
    /** 
     * Mask for highlighting entire device class including device and 
     * service classes. 
     */
    private static final int COD = DEVICE_CLASS | SERV_CLASSES;
    /** Mask for highlighting a bit that shows device discoverable mode. */
    private static final int DISCOVERABLE = 0xff000000;
    /** LIAC bit. */
    private static final int LIAC = 0x01000000;
    /** GIAC bit. */
    private static final int GIAC = 0x02000000;
    /** Uniscoverable. */
    private static final int UNDISCOVERABLE = 0;

    /**
     * Constructs an instance with given value.
     * @param cod value for class of device
     * @param mode value for discoverable mode
     */
    DeviceState(int cod, int mode) {
        data = cod;
        setDiscoverable(mode);
    }

    /**
     * Constructs default state that is undiscoverable and has invalid
     * device class.
     */
    DeviceState() {
        this.data = UNDISCOVERABLE;
    }

    /**
     * Retrieves integer representation.
     * @return integer representation
     */
    int toInt() {
        return data;
    }

    /**
     * Retrieves entire class of device including device and service classes.
     * @return integer value that represents class of device.
     */
    int getCoD() {
        return data & COD;
    }

    /**
     * Returns discoverable mode.
     * @return integer that represents discoverable mode.
     */
    int getDiscoverable() {
        switch (data & DISCOVERABLE) {
            case LIAC: return DiscoveryAgent.LIAC;
            case GIAC: return DiscoveryAgent.GIAC;
            default: return DiscoveryAgent.NOT_DISCOVERABLE;
        }
    }

    /**
     * Sets service classes of device.
     * @param classes new service classes value
     */
    void setServiceClasses(int classes) {
        data =  data & (DISCOVERABLE | DEVICE_CLASS) | classes;
    }

    /**
     * Sets discoverable mode to given one.
     * @param mode discoverable mode to set to
     * @return true if the discoverable mode is supported, otherwise - false
     */
    boolean setDiscoverable(int mode) {
        int bits = 0;
        boolean ret = true;
        switch (mode) {
            case DiscoveryAgent.LIAC:
                bits = LIAC;
                break;
            case DiscoveryAgent.GIAC:
                bits = GIAC;
                break;
            case DiscoveryAgent.NOT_DISCOVERABLE:
                bits = UNDISCOVERABLE;
                break;
            default:
                ret = false;
                break;
        }
        if (ret) {
            data = bits | data & DEVICE_CLASS;
        }
        return ret;
    }

    /**
     * Updates values from new integer representation.
     * @param data new integer representation
     */
    void update(int data) {
        this.data = data;
    }
}