// Copyright 2009, Alvaro J. Iradier
// This file is part of xPressent (J2ME Remote controller).
//
// xPressent is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// xPressent 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 for more details.
//
// You should have received a copy of the GNU General Public License
// along with xPressent. If not, see
* Remember the discType * and process its evaluation in another thread. */ /* public void inquiryCompleted(int discType) { this.discType = discType; synchronized (this) { notify(); } }*/ /* public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { for (int i = 0; i < servRecord.length; i++) { records.addElement(servRecord[i]); } } */ public void serviceSearchCompleted(int transID, int respCode) { // first, find the service search transaction index int index = -1; for (int i = 0; i < searchIDs.length; i++) { if (searchIDs[i] == transID) { index = i; break; } } // error - unexpected transaction index if (index == -1) { System.err.println("Unexpected transaction index: " + transID); // process the error case here } else { searchIDs[index] = -1; } /* * Actually, we do not care about the response code - * if device is not reachable or no records, etc. */ // make sure it was the last transaction for (int i = 0; i < searchIDs.length; i++) { if (searchIDs[i] != -1) { return; } } // ok, all of the transactions are completed synchronized (this) { notify(); } } /** Sets the request to search the devices/services. */ void requestSearch() { synchronized (this) { notify(); } } /** Cancel's the devices/services search. */ void cancelSearch() { synchronized (this) { if (state == DEVICE_SEARCH) { discoveryAgent.cancelInquiry(this); } else if (state == SERVICE_SEARCH) { for (int i = 0; i < searchIDs.length; i++) { discoveryAgent.cancelServiceSearch(searchIDs[i]); } } } } /** Sets the request to load the specified image. */ void requestLoad(String name) { synchronized (this) { imageNameToLoad = name; notify(); } } /** Cancel's the image download. */ void cancelLoad() { /* * The image download process is done by * this class's thread (not by a system one), * so no need to wake up the current thread - * it's running already. */ isDownloadCanceled = true; } /** * Destroy a work with bluetooth - exits the accepting * thread and close notifier. */ void destroy() { synchronized (this) { isClosed = true; isDownloadCanceled = true; notify(); } // wait for acceptor thread is done try { processorThread.join(); } catch (InterruptedException e) { } // ignore } /** * Processes images search/download until component is closed * or system error has happen. */ private synchronized void processImagesSearchDownload() { while (!isClosed) { // wait for new search request from user state = READY; try { wait(); } catch (InterruptedException e) { System.err.println("Unexpected interruption: " + e); return; } // check the component is destroyed if (isClosed) { return; } // search for devices if (!searchDevices()) { return; } else if (devices.size() == 0) { continue; } // search for services now if (!searchServices()) { return; } else if (records.size() == 0) { continue; } // ok, something was found - present the result to user now if (!presentUserSearchResults()) { // services are found, but no names there continue; } // the several download requests may be processed while (true) { // this download is not canceled, right? isDownloadCanceled = false; // ok, wait for download or need to wait for next search try { wait(); } catch (InterruptedException e) { System.err.println("Unexpected interruption: " + e); return; } // check the component is destroyed if (isClosed) { return; } // this means "go to the beginning" if (imageNameToLoad == null) { break; } // load selected image data Image img = loadImage(); // this should never happen - monitor is taken... if (isClosed) { return; } if (isDownloadCanceled) { continue; // may be next image to be download } if (img == null) { parent.informLoadError("Can't load image: " + imageNameToLoad); continue; // may be next image to be download } // ok, show image to user parent.showImage(img, imageNameToLoad); // may be next image to be download continue; } } } /** * Search for bluetooth devices. * * @return false if should end the component work. */ private boolean searchDevices() { // ok, start a new search then state = DEVICE_SEARCH; devices.removeAllElements(); try { discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this); } catch (BluetoothStateException e) { System.err.println("Can't start inquiry now: " + e); parent.informSearchError("Can't start device search"); return true; } try { wait(); // until devices are found } catch (InterruptedException e) { System.err.println("Unexpected interruption: " + e); return false; } // this "wake up" may be caused by 'destroy' call if (isClosed) { return false; } // no?, ok, let's check the return code then switch (discType) { case INQUIRY_ERROR: parent.informSearchError("Device discovering error..."); // fall through case INQUIRY_TERMINATED: // make sure no garbage in found devices list devices.removeAllElements(); // nothing to report - go to next request break; case INQUIRY_COMPLETED: if (devices.size() == 0) { parent.informSearchError("No devices in range"); } // go to service search now break; default: // what kind of system you are?... :( System.err.println("system error:" + " unexpected device discovery code: " + discType); destroy(); return false; } return true; } /** * Search for proper service. * * @return false if should end the component work. */ private boolean searchServices() { state = SERVICE_SEARCH; records.removeAllElements(); searchIDs = new int[devices.size()]; boolean isSearchStarted = false; for (int i = 0; i < devices.size(); i++) { RemoteDevice rd = (RemoteDevice)devices.elementAt(i); try { searchIDs[i] = discoveryAgent.searchServices(attrSet, uuidSet, rd, this); } catch (BluetoothStateException e) { System.err.println("Can't search services for: " + rd.getBluetoothAddress() + " due to " + e); searchIDs[i] = -1; continue; } isSearchStarted = true; } // at least one of the services search should be found if (!isSearchStarted) { parent.informSearchError("Can't search services."); return true; } try { wait(); // until services are found } catch (InterruptedException e) { System.err.println("Unexpected interruption: " + e); return false; } // this "wake up" may be caused by 'destroy' call if (isClosed) { return false; } // actually, no services were found if (records.size() == 0) { parent.informSearchError("No proper services were found"); } return true; } /** * Gets the collection of the images titles (names) * from the services, prepares a hashtable to match * the image name to a services list, presents the images names * to user finally. * * @return false if no names in found services. */ private boolean presentUserSearchResults() { base.clear(); for (int i = 0; i < records.size(); i++) { ServiceRecord sr = (ServiceRecord)records.elementAt(i); // get the attribute with images names DataElement de = sr.getAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID); if (de == null) { System.err.println("Unexpected service - missed attribute"); continue; } // get the images names from this attribute Enumeration deEnum = (Enumeration)de.getValue(); while (deEnum.hasMoreElements()) { de = (DataElement)deEnum.nextElement(); String name = (String)de.getValue(); // name may be stored already Object obj = base.get(name); // that's either the ServiceRecord or Vector if (obj != null) { Vector v; if (obj instanceof ServiceRecord) { v = new Vector(); v.addElement(obj); } else { v = (Vector)obj; } v.addElement(sr); obj = v; } else { obj = sr; } base.put(name, obj); } } return parent.showImagesNames(base); } /** * Loads selected image data. */ private Image loadImage() { if (imageNameToLoad == null) { System.err.println("Error: imageNameToLoad=null"); return null; } // ok, get the list of service records ServiceRecord[] sr = null; Object obj = base.get(imageNameToLoad); if (obj == null) { System.err.println("Error: no record for: " + imageNameToLoad); return null; } else if (obj instanceof ServiceRecord) { sr = new ServiceRecord[] { (ServiceRecord)obj }; } else { Vector v = (Vector)obj; sr = new ServiceRecord[v.size()]; for (int i = 0; i < v.size(); i++) { sr[i] = (ServiceRecord)v.elementAt(i); } } // now try to load the image from each services one by one for (int i = 0; i < sr.length; i++) { StreamConnection conn = null; String url = null; // the process may be canceled if (isDownloadCanceled) { return null; } // first - connect try { url = sr[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false); conn = (StreamConnection)Connector.open(url); } catch (IOException e) { System.err.println("Note: can't connect to: " + url); // ignore continue; } // then open a steam and write a name try { OutputStream out = conn.openOutputStream(); out.write(imageNameToLoad.length()); // length is 1 byte out.write(imageNameToLoad.getBytes()); out.flush(); out.close(); } catch (IOException e) { System.err.println("Can't write to server for: " + url); // close stream connection try { conn.close(); } catch (IOException ee) { } // ignore continue; } // then open a steam and read an image byte[] imgData = null; try { InputStream in = conn.openInputStream(); // read a length first int length = in.read() << 8; length |= in.read(); if (length <= 0) { throw new IOException("Can't read a length"); } // read the image now imgData = new byte[length]; length = 0; while (length != imgData.length) { int n = in.read(imgData, length, imgData.length - length); if (n == -1) { throw new IOException("Can't read a image data"); } length += n; } in.close(); } catch (IOException e) { System.err.println("Can't read from server for: " + url); continue; } finally { // close stream connection anyway try { conn.close(); } catch (IOException e) { } // ignore } // ok, may it's a chance Image img = null; try { img = Image.createImage(imgData, 0, imgData.length); } catch (Exception e) { // may be next time System.err.println("Error: wrong image data from: " + url); continue; } return img; } return null; } } // end of class 'BTImageClient' definition