BluetoothOppObexServerSession.java revision 52236de777c23788df8147de15912a57e8bc36dd
1/* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33package com.android.bluetooth.opp; 34 35import java.io.BufferedOutputStream; 36import java.io.File; 37import java.io.FileNotFoundException; 38import java.io.FileOutputStream; 39import java.io.IOException; 40import java.io.InputStream; 41 42import android.content.ContentValues; 43import android.content.Context; 44import android.content.Intent; 45import android.net.Uri; 46import android.os.Handler; 47import android.os.Message; 48import android.os.PowerManager; 49import android.os.PowerManager.WakeLock; 50import android.util.Log; 51import android.webkit.MimeTypeMap; 52 53import javax.obex.HeaderSet; 54import javax.obex.ObexTransport; 55import javax.obex.Operation; 56import javax.obex.ResponseCodes; 57import javax.obex.ServerRequestHandler; 58import javax.obex.ServerSession; 59 60/** 61 * This class runs as an OBEX server 62 */ 63public class BluetoothOppObexServerSession extends ServerRequestHandler implements 64 BluetoothOppObexSession { 65 66 private static final String TAG = "BtOpp Server"; 67 68 private ObexTransport mTransport; 69 70 private Context mContext; 71 72 private Handler mCallback = null; 73 74 /* status when server is blocking for user/auto confirmation */ 75 private boolean mServerBlocking = true; 76 77 /* the current transfer info */ 78 private BluetoothOppShareInfo mInfo; 79 80 /* info id when we insert the record */ 81 private int mLocalShareInfoId; 82 83 private int mAccepted = BluetoothShare.USER_CONFIRMATION_PENDING; 84 85 private boolean mInterrupted = false; 86 87 private ServerSession mSession; 88 89 private long mTimestamp; 90 91 private BluetoothOppReceiveFileInfo mFileInfo; 92 93 private WakeLock mWakeLock; 94 95 private WakeLock mPartialWakeLock; 96 97 public BluetoothOppObexServerSession(Context context, ObexTransport transport) { 98 mContext = context; 99 mTransport = transport; 100 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 101 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP 102 | PowerManager.ON_AFTER_RELEASE, TAG); 103 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 104 } 105 106 public void unblock() { 107 mServerBlocking = false; 108 } 109 110 /** 111 * Called when connection is accepted from remote, to retrieve the first 112 * Header then wait for user confirmation 113 */ 114 public void preStart() { 115 if (Constants.LOGV) { 116 Log.v(TAG, "acquire full WakeLock"); 117 } 118 mWakeLock.acquire(); 119 try { 120 if (Constants.LOGV) { 121 Log.v(TAG, "Create ServerSession with transport " + mTransport.toString()); 122 } 123 mSession = new ServerSession(mTransport, this, null); 124 } catch (IOException e) { 125 Log.e(TAG, "Create server session error" + e); 126 } 127 } 128 129 /** 130 * Called from BluetoothOppTransfer to start the "Transfer" 131 */ 132 public void start(Handler handler) { 133 if (Constants.LOGV) { 134 Log.v(TAG, "Start!"); 135 } 136 mCallback = handler; 137 138 } 139 140 /** 141 * Called from BluetoothOppTransfer to cancel the "Transfer" Otherwise, 142 * server should end by itself. 143 */ 144 public void stop() { 145 /* 146 * TODO now we implement in a tough way, just close the socket. 147 * maybe need nice way 148 */ 149 if (Constants.LOGV) { 150 Log.v(TAG, "Stop!"); 151 } 152 mInterrupted = true; 153 if (mSession != null) { 154 try { 155 mSession.close(); 156 mTransport.close(); 157 } catch (IOException e) { 158 Log.e(TAG, "close mTransport error" + e); 159 } 160 } 161 if (Constants.LOGVV) { 162 Log.v(TAG, "release WakeLock"); 163 } 164 if (mWakeLock.isHeld()) { 165 mWakeLock.release(); 166 } 167 if (mPartialWakeLock.isHeld()) { 168 mPartialWakeLock.release(); 169 } 170 } 171 172 public void addShare(BluetoothOppShareInfo info) { 173 if (Constants.LOGV) { 174 Log.v(TAG, "addShare for id " + info.mId); 175 } 176 mInfo = info; 177 mFileInfo = processShareInfo(); 178 } 179 180 @Override 181 public int onPut(Operation op) { 182 if (Constants.LOGV) { 183 Log.v(TAG, "onPut " + op.toString()); 184 } 185 HeaderSet request; 186 String name, mimeType; 187 Long length; 188 189 int obexResponse = ResponseCodes.OBEX_HTTP_OK; 190 191 /** 192 * For multiple objects, reject further objects after user deny the 193 * first one 194 */ 195 if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED) { 196 return ResponseCodes.OBEX_HTTP_FORBIDDEN; 197 } 198 199 try { 200 boolean pre_reject = false; 201 request = op.getReceivedHeader(); 202 if (Constants.LOGVV) { 203 logHeader(request); 204 } 205 name = (String)request.getHeader(HeaderSet.NAME); 206 length = (Long)request.getHeader(HeaderSet.LENGTH); 207 mimeType = (String)request.getHeader(HeaderSet.TYPE); 208 209 if (length == 0) { 210 if (Constants.LOGV) { 211 Log.w(TAG, "length is 0, reject the transfer"); 212 } 213 pre_reject = true; 214 obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED; 215 } 216 217 if (name == null || name.equals("")) { 218 if (Constants.LOGV) { 219 Log.w(TAG, "name is null or empty, reject the transfer"); 220 } 221 pre_reject = true; 222 obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST; 223 } 224 225 if (!pre_reject) { 226 /* first we look for Mimetype in Android map */ 227 String extension, type; 228 int dotIndex = name.indexOf('.'); 229 if (dotIndex < 0) { 230 if (Constants.LOGV) { 231 Log.w(TAG, "There is no file extension, reject the transfer"); 232 } 233 pre_reject = true; 234 obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST; 235 } else { 236 extension = name.substring(dotIndex + 1); 237 MimeTypeMap map = MimeTypeMap.getSingleton(); 238 type = map.getMimeTypeFromExtension(extension); 239 if (Constants.LOGVV) { 240 Log.v(TAG, "Mimetype guessed from extension " + extension + " is " 241 + type); 242 } 243 if (type != null) { 244 mimeType = type; 245 246 } else { 247 if (mimeType == null) { 248 if (Constants.LOGV) { 249 Log.w(TAG, "Can't get mimetype, reject the transfer"); 250 } 251 pre_reject = true; 252 obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE; 253 } 254 } 255 if (mimeType != null) { 256 mimeType = mimeType.toLowerCase(); 257 } 258 } 259 } 260 261 if (!pre_reject 262 && (mimeType == null || Constants.mimeTypeMatches(mimeType, 263 Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) { 264 if (Constants.LOGV) { 265 Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer"); 266 } 267 pre_reject = true; 268 obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE; 269 } 270 271 if (pre_reject && obexResponse != ResponseCodes.OBEX_HTTP_OK) { 272 return obexResponse; 273 } 274 275 } catch (IOException e) { 276 Log.e(TAG, "get getReceivedHeaders error " + e); 277 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 278 } 279 280 ContentValues values = new ContentValues(); 281 282 values.put(BluetoothShare.FILENAME_HINT, name); 283 values.put(BluetoothShare.TOTAL_BYTES, length.intValue()); 284 values.put(BluetoothShare.MIMETYPE, mimeType); 285 286 if (mTransport instanceof BluetoothOppRfcommTransport) { 287 String a = ((BluetoothOppRfcommTransport)mTransport).getRemoteAddress(); 288 values.put(BluetoothShare.DESTINATION, a); 289 } else { 290 values.put(BluetoothShare.DESTINATION, "FF:FF:FF:00:00:00"); 291 } 292 293 values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND); 294 values.put(BluetoothShare.TIMESTAMP, mTimestamp); 295 296 boolean needConfirm = true; 297 /** It's not first put if !serverBlocking, so we auto accept it */ 298 if (!mServerBlocking) { 299 values.put(BluetoothShare.USER_CONFIRMATION, 300 BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED); 301 needConfirm = false; 302 } 303 304 Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values); 305 mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1)); 306 307 if (needConfirm) { 308 Intent in = new Intent(BluetoothShare.INCOMING_FILE_CONFIRMATION_REQUEST_ACTION); 309 in.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); 310 mContext.sendBroadcast(in); 311 } 312 313 if (Constants.LOGVV) { 314 Log.v(TAG, "insert contentUri: " + contentUri); 315 Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId); 316 } 317 318 if (Constants.LOGVV) { 319 Log.v(TAG, "acquire partial WakeLock"); 320 } 321 if( mWakeLock.isHeld()) { 322 mPartialWakeLock.acquire(); 323 mWakeLock.release(); 324 } 325 // TODO add server wait timeout 326 mServerBlocking = true; 327 boolean msgSent = false; 328 synchronized (this) { 329 try { 330 331 while (mServerBlocking) { 332 wait(1000); 333 if (mCallback != null && !msgSent) { 334 mCallback.sendMessageDelayed(mCallback 335 .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT), 336 BluetoothOppObexSession.SESSION_TIMEOUT); 337 msgSent = true; 338 if (Constants.LOGVV) { 339 Log.v(TAG, "MSG_CONNECT_TIMEOUT sent"); 340 } 341 } 342 } 343 } catch (InterruptedException e) { 344 if (Constants.LOGVV) { 345 Log.v(TAG, "Interrupted in onPut blocking"); 346 } 347 } 348 } 349 if (Constants.LOGV) { 350 Log.v(TAG, "Server unblocked "); 351 } 352 if (mCallback != null && msgSent) { 353 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); 354 } 355 356 /* we should have mInfo now */ 357 358 /* 359 * TODO check if this mInfo match the one that we insert before server 360 * blocking? just to make sure no error happens 361 */ 362 if (mInfo.mId != mLocalShareInfoId) { 363 Log.e(TAG, "Unexpected error!"); 364 } 365 mAccepted = mInfo.mConfirm; 366 367 if (Constants.LOGVV) { 368 Log.v(TAG, "after confirm: userAccepted=" + mAccepted); 369 } 370 int status = BluetoothShare.STATUS_SUCCESS; 371 372 if (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED 373 || mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED) { 374 /* Confirm or auto-confirm */ 375 376 if (mFileInfo.mFileName == null) { 377 status = mFileInfo.mStatus; 378 /* TODO need to check if this line is correct */ 379 mInfo.mStatus = mFileInfo.mStatus; 380 Constants.updateShareStatus(mContext, mInfo.mId, status); 381 obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 382 383 } 384 385 if (mFileInfo.mFileName != null) { 386 387 ContentValues updateValues = new ContentValues(); 388 contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); 389 updateValues.put(BluetoothShare._DATA, mFileInfo.mFileName); 390 updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING); 391 mContext.getContentResolver().update(contentUri, updateValues, null, null); 392 393 status = receiveFile(mFileInfo, op); 394 /* 395 * TODO map status to obex response code 396 */ 397 if (status != BluetoothShare.STATUS_SUCCESS) { 398 obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 399 } 400 Constants.updateShareStatus(mContext, mInfo.mId, status); 401 } 402 403 if (status == BluetoothShare.STATUS_SUCCESS) { 404 Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SHARE_COMPLETE); 405 msg.obj = mInfo; 406 msg.sendToTarget(); 407 } else { 408 Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SESSION_ERROR); 409 msg.obj = mInfo; 410 msg.sendToTarget(); 411 } 412 } else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED 413 || mAccepted == BluetoothShare.USER_CONFIRMATION_TIMEOUT) { 414 /* user actively deny the inbound transfer */ 415 /* 416 * Note There is a question: what's next if user deny the first obj? 417 * Option 1 :continue prompt for next objects 418 * Option 2 :reject next objects and finish the session 419 * Now we take option 2: 420 */ 421 422 Log.i(TAG, "Rejected incoming request"); 423 status = BluetoothShare.STATUS_FORBIDDEN; 424 Constants.updateShareStatus(mContext, mInfo.mId, status); 425 obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN; 426 Message msg = Message.obtain(mCallback); 427 /* TODO check which message should be sent */ 428 msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED; 429 msg.obj = mInfo; 430 msg.sendToTarget(); 431 } 432 return obexResponse; 433 } 434 435 private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) { 436 /* 437 * implement receive file 438 */ 439 int status = -1; 440 BufferedOutputStream bos = null; 441 442 InputStream is = null; 443 boolean error = false; 444 try { 445 is = op.openInputStream(); 446 } catch (IOException e1) { 447 Log.e(TAG, "Error when openInputStream"); 448 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 449 error = true; 450 } 451 452 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); 453 454 if (!error) { 455 ContentValues updateValues = new ContentValues(); 456 updateValues.put(BluetoothShare._DATA, fileInfo.mFileName); 457 mContext.getContentResolver().update(contentUri, updateValues, null, null); 458 } 459 460 int position = 0; 461 if (!error) { 462 File f = new File(fileInfo.mFileName); 463 try { 464 bos = new BufferedOutputStream(new FileOutputStream(f), 0x10000); 465 } catch (FileNotFoundException e1) { 466 Log.e(TAG, "Error when open file " + f.toString()); 467 status = BluetoothShare.STATUS_FILE_ERROR; 468 error = true; 469 } 470 } 471 472 if (!error) { 473 int outputBufferSize = op.getMaxPacketSize(); 474 byte[] b = new byte[outputBufferSize]; 475 int readLength = 0; 476 long timestamp = 0; 477 try { 478 while ((!mInterrupted) && (position != fileInfo.mLength)) { 479 480 if (Constants.LOGVV) { 481 timestamp = System.currentTimeMillis(); 482 } 483 484 readLength = is.read(b); 485 486 if (readLength == -1) { 487 if (Constants.LOGV) { 488 Log.v(TAG, "Receive file reached stream end at position" + position); 489 } 490 break; 491 } 492 493 bos.write(b, 0, readLength); 494 position += readLength; 495 496 if (Constants.USE_EMULATOR_DEBUG) { 497 synchronized (this) { 498 try { 499 wait(300); 500 } catch (InterruptedException e) { 501 status = BluetoothShare.STATUS_CANCELED; 502 mInterrupted = true; 503 if (Constants.LOGVV) { 504 Log.v(TAG, "ReceiveFile interrupted when receive file " 505 + fileInfo.mFileName + " at " + position + " of " 506 + position); 507 } 508 Constants.updateShareStatus(mContext, mInfo.mId, status); 509 } 510 } 511 } 512 513 if (Constants.LOGVV) { 514 Log.v(TAG, "Receive file position = " + position + " readLength " 515 + readLength + " bytes took " 516 + (System.currentTimeMillis() - timestamp) + " ms"); 517 } 518 519 ContentValues updateValues = new ContentValues(); 520 updateValues.put(BluetoothShare.CURRENT_BYTES, position); 521 mContext.getContentResolver().update(contentUri, updateValues, null, null); 522 } 523 } catch (IOException e1) { 524 Log.e(TAG, "Error when receiving file"); 525 status = BluetoothShare.STATUS_OBEX_DATA_ERROR; 526 error = true; 527 } 528 } 529 530 if (mInterrupted) { 531 if (Constants.LOGV) { 532 Log.v(TAG, "receiving file interrupted by user."); 533 } 534 status = BluetoothShare.STATUS_CANCELED; 535 } else { 536 if (position == fileInfo.mLength) { 537 if (Constants.LOGV) { 538 Log.v(TAG, "Receiving file completed for " + fileInfo.mFileName); 539 } 540 status = BluetoothShare.STATUS_SUCCESS; 541 } else { 542 if (Constants.LOGV) { 543 Log.v(TAG, "Reading file failed at " + position + " of " + fileInfo.mLength); 544 } 545 if (status == -1) { 546 status = BluetoothShare.STATUS_UNKNOWN_ERROR; 547 } 548 } 549 } 550 551 if (bos != null) { 552 try { 553 bos.close(); 554 } catch (IOException e) { 555 Log.e(TAG, "Error when closing stream after send"); 556 } 557 } 558 return status; 559 } 560 561 private BluetoothOppReceiveFileInfo processShareInfo() { 562 if (Constants.LOGV) { 563 Log.v(TAG, "processShareInfo() " + mInfo.mId); 564 } 565 BluetoothOppReceiveFileInfo fileInfo = BluetoothOppReceiveFileInfo.generateFileInfo( 566 mContext, mInfo.mId); 567 if (Constants.LOGVV) { 568 Log.v(TAG, "Generate BluetoothOppReceiveFileInfo:"); 569 Log.v(TAG, "filename :" + fileInfo.mFileName); 570 Log.v(TAG, "length :" + fileInfo.mLength); 571 Log.v(TAG, "status :" + fileInfo.mStatus); 572 } 573 return fileInfo; 574 } 575 576 @Override 577 public int onConnect(HeaderSet request, HeaderSet reply) { 578 579 if (Constants.LOGV) { 580 Log.v(TAG, "onConnect"); 581 } 582 if (Constants.LOGVV) { 583 logHeader(request); 584 } 585 586 mTimestamp = System.currentTimeMillis(); 587 return ResponseCodes.OBEX_HTTP_OK; 588 } 589 590 @Override 591 public void onDisconnect(HeaderSet req, HeaderSet resp) { 592 593 if (Constants.LOGV) { 594 Log.v(TAG, "onDisconnect"); 595 } 596 resp.responseCode = ResponseCodes.OBEX_HTTP_OK; 597 598 if (Constants.LOGV) { 599 Log.v(TAG, "release WakeLock"); 600 } 601 if (mWakeLock.isHeld()) { 602 mWakeLock.release(); 603 } 604 if (mPartialWakeLock.isHeld()) { 605 mPartialWakeLock.release(); 606 } 607 /* onDisconnect could happen even before start() where mCallback is set */ 608 if (mCallback != null) { 609 Message msg = Message.obtain(mCallback); 610 msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE; 611 msg.obj = mInfo; 612 msg.sendToTarget(); 613 } 614 } 615 616 @Override 617 public void onClose() { 618 } 619 620 private void logHeader(HeaderSet hs) { 621 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 622 try { 623 624 Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT)); 625 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 626 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 627 Log.v(TAG, "LENGTH : " + hs.getHeader(HeaderSet.LENGTH)); 628 Log.v(TAG, "TIME_ISO_8601 : " + hs.getHeader(HeaderSet.TIME_ISO_8601)); 629 Log.v(TAG, "TIME_4_BYTE : " + hs.getHeader(HeaderSet.TIME_4_BYTE)); 630 Log.v(TAG, "DESCRIPTION : " + hs.getHeader(HeaderSet.DESCRIPTION)); 631 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 632 Log.v(TAG, "HTTP : " + hs.getHeader(HeaderSet.HTTP)); 633 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 634 Log.v(TAG, "OBJECT_CLASS : " + hs.getHeader(HeaderSet.OBJECT_CLASS)); 635 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 636 } catch (IOException e) { 637 Log.e(TAG, "dump HeaderSet error " + e); 638 } 639 640 } 641} 642