View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2006-2008 Vlad Skarzhevskyy
4    * 
5    *  Licensed to the Apache Software Foundation (ASF) under one
6    *  or more contributor license agreements.  See the NOTICE file
7    *  distributed with this work for additional information
8    *  regarding copyright ownership.  The ASF licenses this file
9    *  to you under the Apache License, Version 2.0 (the
10   *  "License"); you may not use this file except in compliance
11   *  with the License.  You may obtain a copy of the License at
12   *
13   *    http://www.apache.org/licenses/LICENSE-2.0
14   *
15   *  Unless required by applicable law or agreed to in writing,
16   *  software distributed under the License is distributed on an
17   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   *  KIND, either express or implied.  See the License for the
19   *  specific language governing permissions and limitations
20   *  under the License.
21   *
22   *  @author vlads
23   *  @version $Id: Main.java 2471 2008-12-01 03:44:20Z skarzhevskyy $
24   */
25  package net.sf.bluecove.obex;
26  
27  import java.awt.BorderLayout;
28  import java.awt.Dimension;
29  import java.awt.GridBagConstraints;
30  import java.awt.GridBagLayout;
31  import java.awt.Image;
32  import java.awt.Toolkit;
33  import java.awt.event.ActionEvent;
34  import java.awt.event.ActionListener;
35  import java.awt.event.MouseEvent;
36  import java.awt.event.MouseListener;
37  import java.io.ByteArrayOutputStream;
38  import java.io.File;
39  import java.io.FileInputStream;
40  import java.io.InputStream;
41  import java.net.URL;
42  import java.util.Enumeration;
43  import java.util.Hashtable;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Vector;
47  
48  import javax.bluetooth.LocalDevice;
49  import javax.bluetooth.RemoteDevice;
50  import javax.swing.BorderFactory;
51  import javax.swing.Box;
52  import javax.swing.BoxLayout;
53  import javax.swing.ImageIcon;
54  import javax.swing.JButton;
55  import javax.swing.JComboBox;
56  import javax.swing.JFileChooser;
57  import javax.swing.JFrame;
58  import javax.swing.JLabel;
59  import javax.swing.JPanel;
60  import javax.swing.JProgressBar;
61  import javax.swing.SwingUtilities;
62  import javax.swing.Timer;
63  import javax.swing.border.EmptyBorder;
64  
65  /**
66   * 
67   */
68  public class Main extends JFrame implements ActionListener, UserInteraction {
69  
70  	private static final long serialVersionUID = 1L;
71  
72  	private static final int BLUETOOTH_DISCOVERY_STD_SEC = 11;
73  
74  	private JLabel iconLabel;
75  
76  	private String status;
77  
78  	JProgressBar progressBar;
79  
80  	private ImageIcon btIcon;
81  
82  	private ImageIcon transferIcon;
83  
84  	private ImageIcon searchIcon;
85  
86  	private ImageIcon downloadIcon;
87  
88  	private JComboBox cbDevices;
89  
90  	private JButton btFindDevice;
91  
92  	private JButton btSend;
93  
94  	private JButton btCancel;
95  
96  	private BluetoothInquirer bluetoothInquirer;
97  
98  	private Hashtable devices = new Hashtable();
99  
100 	private JFileChooser fileChooser;
101 
102 	private String fileName;
103 
104 	private byte[] data;
105 
106 	private List queue = new Vector();
107 
108 	protected Main() {
109 		super("BlueCove OBEX Push");
110 		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
111 
112 		Image btImage = Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/icon.png"));
113 		btIcon = new ImageIcon(btImage);
114 		transferIcon = new ImageIcon((Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/transfer.png"))));
115 		searchIcon = new ImageIcon((Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/search.png"))));
116 		downloadIcon = new ImageIcon((Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/download.png"))));
117 
118 		this.setIconImage(btImage);
119 
120 		JPanel contentPane = (JPanel) this.getContentPane();
121 		contentPane.setLayout(new BorderLayout(10, 10));
122 		contentPane.setBorder(new EmptyBorder(10, 10, 10, 10));
123 		contentPane.setTransferHandler(new DropTransferHandler(this));
124 
125 		contentPane.addMouseListener(new MouseDoubleClickListener());
126 
127 		JPanel progressPanel = new JPanel();
128 		progressPanel.setLayout(new GridBagLayout());
129 		GridBagConstraints c = new GridBagConstraints();
130 
131 		iconLabel = new JLabel();
132 		iconLabel.setIcon(btIcon);
133 		c.fill = GridBagConstraints.BOTH;
134 		c.weightx = 1.0;
135 		progressPanel.add(iconLabel, c);
136 
137 		progressBar = new JProgressBar(0, 100);
138 		progressBar.setValue(0);
139 		progressBar.setStringPainted(true);
140 
141 		c.fill = GridBagConstraints.HORIZONTAL;
142 		c.gridwidth = GridBagConstraints.REMAINDER;
143 		progressPanel.add(progressBar, c);
144 
145 		getContentPane().add(progressPanel, BorderLayout.NORTH);
146 
147 		JPanel optionsPanel = new JPanel();
148 
149 		JLabel deviceLabel = new JLabel("Send to:");
150 		optionsPanel.add(deviceLabel);
151 		cbDevices = new JComboBox();
152 		cbDevices.addItem("{no device found}");
153 		cbDevices.setEnabled(false);
154 		optionsPanel.add(cbDevices);
155 		optionsPanel.add(btFindDevice = new JButton("Find"));
156 		btFindDevice.addActionListener(this);
157 
158 		getContentPane().add(optionsPanel, BorderLayout.CENTER);
159 
160 		JPanel actionPanel = new JPanel();
161 		actionPanel.setLayout(new BoxLayout(actionPanel, BoxLayout.LINE_AXIS));
162 		actionPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
163 		actionPanel.add(Box.createHorizontalGlue());
164 		actionPanel.add(btSend = new JButton("Send"));
165 		btSend.addActionListener(this);
166 		actionPanel.add(Box.createRigidArea(new Dimension(10, 0)));
167 		actionPanel.add(btCancel = new JButton("Cancel"));
168 		btCancel.addActionListener(this);
169 
170 		contentPane.add(actionPanel, BorderLayout.SOUTH);
171 		btSend.setEnabled(false);
172 		String selected = Persistence.loadDevices(devices);
173 		updateDevices(selected);
174 	}
175 
176 	private static void createAndShowGUI(final String[] args) {
177 		final Main app = new Main();
178 		app.pack();
179 		app.center();
180 		app.setVisible(true);
181 		SwingUtilities.invokeLater(new Runnable() {
182 			public void run() {
183 				if (app.initializeBlueCove()) {
184 					if (args.length != 0) {
185 						app.downloadFile(args[0]);
186 					}
187 				}
188 			}
189 		});
190 	}
191 
192 	public static void main(final String[] args) {
193 		SwingUtilities.invokeLater(new Runnable() {
194 			public void run() {
195 				createAndShowGUI(args);
196 			}
197 		});
198 	}
199 
200 	private void center() {
201 		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
202 		this.setLocation(((screenSize.width - this.getWidth()) / 2), ((screenSize.height - this.getHeight()) / 2));
203 	}
204 
205 	public void showStatus(final String message) {
206 		setStatus(message);
207 	}
208 
209 	protected void setStatus(final String message) {
210 		status = message;
211 		progressBar.setString(message);
212 	}
213 
214 	public void setProgressMaximum(int n) {
215 		progressBar.setMaximum(n);
216 	}
217 
218 	public void setProgressValue(int n) {
219 		progressBar.setValue(n);
220 		SwingUtilities.invokeLater(new Runnable() {
221 			public void run() {
222 				progressBar.setString(status);
223 			}
224 		});
225 	}
226 
227 	public void setProgressDone() {
228 		progressBar.setValue(0);
229 	}
230 
231 	protected void disabledBluetooth() {
232 		btFindDevice.setEnabled(false);
233 		cbDevices.setEnabled(false);
234 		setStatus("BlueCove not avalable");
235 		btSend.setEnabled(false);
236 		iconLabel.setIcon(new ImageIcon((Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/bt-off.png")))));
237 	}
238 
239 	protected boolean initializeBlueCove() {
240 		try {
241 			LocalDevice localDevice = LocalDevice.getLocalDevice();
242 			if ("000000000000".equals(localDevice.getBluetoothAddress())) {
243 				throw new Exception();
244 			}
245 			bluetoothInquirer = new BluetoothInquirer(this);
246 			setStatus("BlueCove Ready");
247 			return true;
248 		} catch (Throwable e) {
249 			Logger.error(e);
250 			disabledBluetooth();
251 			return false;
252 		}
253 	}
254 
255 	public void actionPerformed(ActionEvent e) {
256 		if (e.getSource() == btFindDevice) {
257 			bluetoothDiscovery();
258 		} else if (e.getSource() == btCancel) {
259 			shutdown();
260 			System.exit(0);
261 		} else if (e.getSource() == btSend) {
262 			obexSend();
263 		}
264 	}
265 
266 	private class MouseDoubleClickListener implements MouseListener {
267 
268 		private long firstClick = 0;
269 
270 		public void mouseClicked(MouseEvent e) {
271 			long now = System.currentTimeMillis();
272 			if ((firstClick != 0) && (firstClick - now < 1000)) {
273 				fireDoubleClick();
274 			} else {
275 				firstClick = now;
276 			}
277 
278 		}
279 
280 		public void mouseEntered(MouseEvent e) {
281 			firstClick = 0;
282 		}
283 
284 		public void mouseExited(MouseEvent e) {
285 			firstClick = 0;
286 		}
287 
288 		public void mousePressed(MouseEvent e) {
289 		}
290 
291 		public void mouseReleased(MouseEvent e) {
292 		}
293 
294 	}
295 
296 	public void fireDoubleClick() {
297 		if (fileChooser == null) {
298 			fileChooser = new JFileChooser();
299 			fileChooser.setDialogTitle("Select File to send...");
300 			fileChooser.setCurrentDirectory(new File(Persistence.getProperty("recentDirectory", ".")));
301 		}
302 		int returnVal = fileChooser.showOpenDialog(Main.this);
303 		if (returnVal == JFileChooser.APPROVE_OPTION) {
304 			Persistence.setProperty("recentDirectory", fileChooser.getCurrentDirectory().getAbsolutePath());
305 			downloadFile(DropTransferHandler.getCanonicalFileURL(fileChooser.getSelectedFile()));
306 			saveConfig();
307 		}
308 
309 	}
310 
311 	private void selectNextFile() {
312 		if (queue.size() > 0) {
313 			String url = (String) queue.remove(0);
314 			downloadFile(url);
315 		}
316 	}
317 
318 	public void queueFile(String url) {
319 		queue.add(url);
320 	}
321 
322 	private void saveConfig() {
323 		Persistence.storeDevices(devices, getSelectedDeviceAddress());
324 	}
325 
326 	private class DiscoveryTimerListener implements ActionListener {
327 		int seconds = 0;
328 
329 		public void actionPerformed(ActionEvent e) {
330 			if (seconds < BLUETOOTH_DISCOVERY_STD_SEC) {
331 				seconds++;
332 				setProgressValue(seconds);
333 			}
334 		}
335 	}
336 
337 	private void addDevice(String btAddress, String name, String obexUrl) {
338 		String key = btAddress.toLowerCase();
339 		DeviceInfo di = (DeviceInfo) devices.get(key);
340 		if (di == null) {
341 			di = new DeviceInfo();
342 		}
343 		di.btAddress = btAddress;
344 		// Update name if one found
345 		if (di.name == null) {
346 			di.name = name;
347 		} else if (btAddress.equals(di.name)) {
348 			di.name = name;
349 		}
350 		di.obexUrl = obexUrl;
351 		di.obexServiceFound = true;
352 		devices.put(key, di);
353 	}
354 
355 	private void updateDevices(String selected) {
356 		cbDevices.removeAllItems();
357 		if (devices.size() == 0) {
358 			cbDevices.addItem("{no device found}");
359 			btSend.setEnabled(false);
360 			cbDevices.setEnabled(false);
361 		} else {
362 			for (Enumeration i = devices.keys(); i.hasMoreElements();) {
363 				String addr = (String) i.nextElement();
364 				DeviceInfo di = (DeviceInfo) devices.get(addr);
365 				cbDevices.addItem(di);
366 				if ((selected != null) && (selected.equals(di.btAddress))) {
367 					cbDevices.setSelectedItem(di);
368 				}
369 			}
370 			cbDevices.setEnabled(true);
371 			btSend.setEnabled(true);
372 		}
373 	}
374 
375 	private void bluetoothDiscovery() {
376 		final Timer timer = new Timer(1000, new DiscoveryTimerListener());
377 		progressBar.setMaximum(BLUETOOTH_DISCOVERY_STD_SEC);
378 		setProgressValue(0);
379 		Thread t = new Thread() {
380 			public void run() {
381 				if (bluetoothInquirer.startInquiry()) {
382 					iconLabel.setIcon(searchIcon);
383 					setStatus("Bluetooth discovery started");
384 					btFindDevice.setEnabled(false);
385 					btSend.setEnabled(false);
386 					timer.start();
387 					while (bluetoothInquirer.inquiring) {
388 						try {
389 							Thread.sleep(1000);
390 						} catch (Exception e) {
391 						}
392 					}
393 					timer.stop();
394 					// setStatus("Bluetooth discovery finished");
395 
396 					setProgressValue(0);
397 					int idx = 0;
398 					progressBar.setMaximum(bluetoothInquirer.devices.size());
399 					for (Iterator iter = bluetoothInquirer.devices.iterator(); iter.hasNext();) {
400 						RemoteDevice dev = (RemoteDevice) iter.next();
401 						String obexUrl = bluetoothInquirer.findOBEX(dev.getBluetoothAddress());
402 						if (obexUrl != null) {
403 							Logger.debug("found obex url", obexUrl);
404 							addDevice(dev.getBluetoothAddress(), BluetoothInquirer.getFriendlyName(dev), obexUrl);
405 						}
406 						idx++;
407 						setProgressValue(idx);
408 					}
409 					setProgressValue(0);
410 					saveConfig();
411 					updateDevices(null);
412 					btFindDevice.setEnabled(true);
413 					btSend.setEnabled(true);
414 					iconLabel.setIcon(btIcon);
415 				}
416 			}
417 		};
418 		t.start();
419 	}
420 
421 	private String blueSoleilFindOBEX(String btAddress, String obexUrl) {
422 		if ("bluesoleil".equals(LocalDevice.getProperty("bluecove.stack"))) {
423 			RemoteDevice dev = new RemoteDeviceExt(btAddress);
424 			String foundObexUrl = bluetoothInquirer.findOBEX(dev.getBluetoothAddress());
425 			if (foundObexUrl != null) {
426 				Logger.debug("found", btAddress);
427 				addDevice(dev.getBluetoothAddress(), BluetoothInquirer.getFriendlyName(dev), foundObexUrl);
428 			}
429 			return foundObexUrl;
430 		}
431 		return obexUrl;
432 	}
433 
434 	private DeviceInfo getSelectedDevice() {
435 		Object o = cbDevices.getSelectedItem();
436 		if ((o == null) || !(o instanceof DeviceInfo)) {
437 			return null;
438 		}
439 		return (DeviceInfo) o;
440 	}
441 
442 	private String getSelectedDeviceAddress() {
443 		DeviceInfo d = getSelectedDevice();
444 		if (d == null) {
445 			return null;
446 		}
447 		return d.btAddress;
448 	}
449 
450 	private void obexSend() {
451 		if (fileName == null) {
452 			setStatus("No file selected");
453 			return;
454 		}
455 		final DeviceInfo d = getSelectedDevice();
456 		if (d == null) {
457 			setStatus("No Device selected");
458 			return;
459 		}
460 		final ObexBluetoothClient o = new ObexBluetoothClient(this, fileName, data);
461 		Thread t = new Thread() {
462 			public void run() {
463 				btSend.setEnabled(false);
464 				iconLabel.setIcon(transferIcon);
465 				String obexUrl = d.obexUrl;
466 				if (!d.obexServiceFound) {
467 					obexUrl = blueSoleilFindOBEX(d.btAddress, obexUrl);
468 				}
469 				if (obexUrl != null) {
470 					if (o.obexPut(obexUrl)) {
471 						selectNextFile();
472 					}
473 				} else {
474 					setStatus("Service not found");
475 				}
476 				btSend.setEnabled(true);
477 				iconLabel.setIcon(btIcon);
478 				saveConfig();
479 			}
480 
481 		};
482 		t.start();
483 	}
484 
485 	private static String simpleFileName(String filePath) {
486 		int idx = filePath.lastIndexOf('/');
487 		if (idx == -1) {
488 			idx = filePath.lastIndexOf('\\');
489 		}
490 		if (idx == -1) {
491 			return filePath;
492 		}
493 		return filePath.substring(idx + 1);
494 	}
495 
496 	void downloadFile(final String filePath) {
497 		Thread t = new Thread() {
498 			public void run() {
499 				InputStream is = null;
500 				try {
501 					iconLabel.setIcon(downloadIcon);
502 					String path = filePath;
503 					String inputFileName;
504 					File file = new File(filePath);
505 					if (file.exists()) {
506 						is = new FileInputStream(file);
507 						inputFileName = file.getName();
508 					} else {
509 						URL url = new URL(path);
510 						is = url.openConnection().getInputStream();
511 						inputFileName = url.getFile();
512 					}
513 					ByteArrayOutputStream bos = new ByteArrayOutputStream();
514 					byte[] buffer = new byte[0xFF];
515 					int i = is.read(buffer);
516 					int done = 0;
517 					while (i != -1) {
518 						bos.write(buffer, 0, i);
519 						done += i;
520 						// setProgressValue(done);
521 						i = is.read(buffer);
522 					}
523 					data = bos.toByteArray();
524 					fileName = simpleFileName(inputFileName);
525 					setStatus((data.length / 1024) + "k " + fileName);
526 				} catch (Throwable e) {
527 					Logger.error(e);
528 					setStatus("Download error " + e.getMessage());
529 				} finally {
530 					IOUtils.closeQuietly(is);
531 					iconLabel.setIcon(btIcon);
532 				}
533 			}
534 		};
535 		t.start();
536 
537 	}
538 
539 	private void shutdown() {
540 		if (bluetoothInquirer != null) {
541 			bluetoothInquirer.shutdown();
542 			bluetoothInquirer = null;
543 		}
544 	}
545 }