• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
33 package com.android.bluetooth.opp;
34 
35 import android.bluetooth.BluetoothProfile;
36 import android.bluetooth.BluetoothProtoEnums;
37 import android.content.ContentValues;
38 import android.content.Context;
39 import android.net.Uri;
40 import android.os.Handler;
41 import android.os.Message;
42 import android.os.PowerManager;
43 import android.os.PowerManager.WakeLock;
44 import android.os.Process;
45 import android.os.SystemClock;
46 import android.util.Log;
47 
48 import com.android.bluetooth.BluetoothMethodProxy;
49 import com.android.bluetooth.BluetoothMetricsProto;
50 import com.android.bluetooth.BluetoothStatsLog;
51 import com.android.bluetooth.btservice.MetricsLogger;
52 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils;
53 import com.android.obex.ClientOperation;
54 import com.android.obex.ClientSession;
55 import com.android.obex.HeaderSet;
56 import com.android.obex.ObexTransport;
57 import com.android.obex.ResponseCodes;
58 
59 import com.google.common.annotations.VisibleForTesting;
60 
61 import java.io.BufferedInputStream;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.io.OutputStream;
65 
66 /** This class runs as an OBEX client */
67 // Next tag value for ContentProfileErrorReportUtils.report(): 17
68 public class BluetoothOppObexClientSession implements BluetoothOppObexSession {
69 
70     private static final String TAG = "BtOppObexClient";
71 
72     private ClientThread mThread;
73 
74     private ObexTransport mTransport;
75 
76     private Context mContext;
77 
78     private volatile boolean mInterrupted;
79 
80     @VisibleForTesting volatile boolean mWaitingForRemote;
81 
82     private int mNumFilesAttemptedToSend;
83 
BluetoothOppObexClientSession(Context context, ObexTransport transport)84     public BluetoothOppObexClientSession(Context context, ObexTransport transport) {
85         if (transport == null) {
86             throw new NullPointerException("transport is null");
87         }
88         mContext = context;
89         mTransport = transport;
90     }
91 
92     @Override
start(Handler handler, int numShares)93     public void start(Handler handler, int numShares) {
94         Log.d(TAG, "Start!");
95         mThread = new ClientThread(mContext, mTransport, numShares, handler);
96         mThread.start();
97     }
98 
99     @Override
stop()100     public void stop() {
101         Log.d(TAG, "Stop!");
102         if (mThread != null) {
103             mInterrupted = true;
104             Log.v(TAG, "Interrupt thread to terminate it");
105             mThread.interrupt();
106             mThread = null;
107         }
108         BluetoothOppUtility.cancelNotification(mContext);
109     }
110 
111     @Override
addShare(BluetoothOppShareInfo share)112     public void addShare(BluetoothOppShareInfo share) {
113         mThread.addShare(share);
114     }
115 
116     @VisibleForTesting
readFully(InputStream is, byte[] buffer, int size)117     static int readFully(InputStream is, byte[] buffer, int size) throws IOException {
118         int done = 0;
119         while (done < size) {
120             int got = is.read(buffer, done, size - done);
121             if (got <= 0) {
122                 break;
123             }
124             done += got;
125         }
126         return done;
127     }
128 
129     @VisibleForTesting
130     class ClientThread extends Thread {
131 
132         private static final int SLEEP_TIME = 500;
133 
134         private Context mContext1;
135 
136         private BluetoothOppShareInfo mInfo;
137 
138         private volatile boolean mWaitingForShare;
139 
140         private ObexTransport mTransport1;
141 
142         @VisibleForTesting ClientSession mCs;
143 
144         private WakeLock mWakeLock;
145 
146         private BluetoothOppSendFileInfo mFileInfo = null;
147 
148         private boolean mConnected = false;
149 
150         private int mNumShares;
151         private final Handler mCallbackHandler;
152 
ClientThread( Context context, ObexTransport transport, int initialNumShares, Handler callback)153         ClientThread(
154                 Context context, ObexTransport transport, int initialNumShares, Handler callback) {
155             super("BtOpp ClientThread");
156             mContext1 = context;
157             mTransport1 = transport;
158             mWaitingForShare = true;
159             mWaitingForRemote = false;
160             mNumShares = initialNumShares;
161             PowerManager pm = mContext.getSystemService(PowerManager.class);
162             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
163             mCallbackHandler = callback;
164         }
165 
addShare(BluetoothOppShareInfo info)166         public void addShare(BluetoothOppShareInfo info) {
167             mInfo = info;
168             mFileInfo = processShareInfo();
169             mWaitingForShare = false;
170         }
171 
172         @Override
run()173         public void run() {
174             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
175 
176             Log.v(TAG, "acquire partial WakeLock");
177             mWakeLock.acquire();
178 
179             try {
180                 Thread.sleep(100);
181             } catch (InterruptedException e1) {
182                 ContentProfileErrorReportUtils.report(
183                         BluetoothProfile.OPP,
184                         BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
185                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
186                         0);
187                 Log.v(TAG, "Client thread was interrupted (1), exiting");
188                 mInterrupted = true;
189             }
190             if (!mInterrupted) {
191                 connect(mNumShares);
192             }
193 
194             mNumFilesAttemptedToSend = 0;
195             while (!mInterrupted) {
196                 if (!mWaitingForShare) {
197                     doSend();
198                 } else {
199                     try {
200                         Log.d(TAG, "Client thread waiting for next share, sleep for " + SLEEP_TIME);
201                         Thread.sleep(SLEEP_TIME);
202                     } catch (InterruptedException e) {
203                         ContentProfileErrorReportUtils.report(
204                                 BluetoothProfile.OPP,
205                                 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
206                                 BluetoothStatsLog
207                                         .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
208                                 1);
209                     }
210                 }
211             }
212             disconnect();
213 
214             if (mWakeLock.isHeld()) {
215                 Log.v(TAG, "release partial WakeLock");
216                 mWakeLock.release();
217             }
218 
219             if (mNumFilesAttemptedToSend > 0) {
220                 // Log outgoing OPP transfer if more than one file is accepted by remote
221                 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.OPP);
222             }
223             Message msg = Message.obtain(mCallbackHandler);
224             msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE;
225             msg.obj = mInfo;
226             msg.sendToTarget();
227         }
228 
disconnect()229         private void disconnect() {
230             try {
231                 if (mCs != null) {
232                     mCs.disconnect(null);
233                 }
234                 mCs = null;
235                 Log.d(TAG, "OBEX session disconnected");
236             } catch (IOException e) {
237                 ContentProfileErrorReportUtils.report(
238                         BluetoothProfile.OPP,
239                         BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
240                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
241                         2);
242                 Log.w(TAG, "OBEX session disconnect error" + e);
243             }
244             try {
245                 if (mCs != null) {
246                     Log.d(TAG, "OBEX session close mCs");
247                     mCs.close();
248                     Log.d(TAG, "OBEX session closed");
249                 }
250             } catch (IOException e) {
251                 ContentProfileErrorReportUtils.report(
252                         BluetoothProfile.OPP,
253                         BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
254                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
255                         3);
256                 Log.w(TAG, "OBEX session close error" + e);
257             }
258             if (mTransport1 != null) {
259                 try {
260                     mTransport1.close();
261                 } catch (IOException e) {
262                     ContentProfileErrorReportUtils.report(
263                             BluetoothProfile.OPP,
264                             BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
265                             BluetoothStatsLog
266                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
267                             4);
268                     Log.e(TAG, "mTransport.close error");
269                 }
270             }
271         }
272 
connect(int numShares)273         private void connect(int numShares) {
274             Log.d(TAG, "Create ClientSession with transport " + mTransport1.toString());
275             try {
276                 mCs = new ClientSession(mTransport1);
277                 mConnected = true;
278             } catch (IOException e1) {
279                 ContentProfileErrorReportUtils.report(
280                         BluetoothProfile.OPP,
281                         BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
282                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
283                         5);
284                 Log.e(TAG, "OBEX session create error");
285             }
286             if (mConnected) {
287                 mConnected = false;
288                 HeaderSet hs = new HeaderSet();
289                 hs.setHeader(HeaderSet.COUNT, (long) numShares);
290                 synchronized (this) {
291                     mWaitingForRemote = true;
292                 }
293                 try {
294                     mCs.connect(hs);
295                     Log.d(TAG, "OBEX session created");
296                     mConnected = true;
297                 } catch (IOException e) {
298                     ContentProfileErrorReportUtils.report(
299                             BluetoothProfile.OPP,
300                             BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
301                             BluetoothStatsLog
302                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
303                             6);
304                     Log.e(TAG, "OBEX session connect error");
305                 }
306             }
307             synchronized (this) {
308                 mWaitingForRemote = false;
309             }
310         }
311 
doSend()312         private void doSend() {
313 
314             int status = BluetoothShare.STATUS_SUCCESS;
315 
316             /* connection is established too fast to get first mInfo */
317             while (mFileInfo == null) {
318                 try {
319                     Thread.sleep(50);
320                 } catch (InterruptedException e) {
321                     ContentProfileErrorReportUtils.report(
322                             BluetoothProfile.OPP,
323                             BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
324                             BluetoothStatsLog
325                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
326                             7);
327                     status = BluetoothShare.STATUS_CANCELED;
328                 }
329             }
330             if (!mConnected) {
331                 // Obex connection error
332                 status = BluetoothShare.STATUS_CONNECTION_ERROR;
333             }
334             if (status == BluetoothShare.STATUS_SUCCESS) {
335                 /* do real send */
336                 if (mFileInfo.mFileName != null) {
337                     status = sendFile(mFileInfo);
338                 } else {
339                     /* this is invalid request */
340                     status = mFileInfo.mStatus;
341                 }
342                 mWaitingForShare = true;
343             } else {
344                 Constants.updateShareStatus(mContext1, mInfo.mId, status);
345             }
346 
347             Message msg = Message.obtain(mCallbackHandler);
348             msg.what =
349                     (status == BluetoothShare.STATUS_SUCCESS)
350                             ? BluetoothOppObexSession.MSG_SHARE_COMPLETE
351                             : BluetoothOppObexSession.MSG_SESSION_ERROR;
352             mInfo.mStatus = status;
353             msg.obj = mInfo;
354             msg.sendToTarget();
355         }
356 
357         /*
358          * Validate this ShareInfo
359          */
processShareInfo()360         private BluetoothOppSendFileInfo processShareInfo() {
361             Log.v(TAG, "Client thread processShareInfo() " + mInfo.mId);
362 
363             BluetoothOppSendFileInfo fileInfo = BluetoothOppUtility.getSendFileInfo(mInfo.mUri);
364             if (fileInfo.mFileName == null || fileInfo.mLength == 0) {
365                 Log.v(TAG, "BluetoothOppSendFileInfo get invalid file");
366                 Constants.updateShareStatus(mContext1, mInfo.mId, fileInfo.mStatus);
367 
368             } else {
369                 Log.v(TAG, "Generate BluetoothOppSendFileInfo:");
370                 Log.v(TAG, "filename  :" + fileInfo.mFileName);
371                 Log.v(TAG, "length    :" + fileInfo.mLength);
372                 Log.v(TAG, "mimetype  :" + fileInfo.mMimetype);
373 
374                 ContentValues updateValues = new ContentValues();
375                 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
376 
377                 updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
378                 updateValues.put(BluetoothShare.TOTAL_BYTES, fileInfo.mLength);
379                 updateValues.put(BluetoothShare.MIMETYPE, fileInfo.mMimetype);
380                 BluetoothMethodProxy.getInstance()
381                         .contentResolverUpdate(
382                                 mContext1.getContentResolver(),
383                                 contentUri,
384                                 updateValues,
385                                 null,
386                                 null);
387             }
388             return fileInfo;
389         }
390 
391         @VisibleForTesting
sendFile(BluetoothOppSendFileInfo fileInfo)392         int sendFile(BluetoothOppSendFileInfo fileInfo) {
393             boolean error = false;
394             int responseCode = -1;
395             long position = 0;
396             int status = BluetoothShare.STATUS_SUCCESS;
397             Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
398             ContentValues updateValues;
399             HeaderSet request = new HeaderSet();
400             ClientOperation putOperation = null;
401             OutputStream outputStream = null;
402             InputStream inputStream = null;
403             try {
404                 synchronized (this) {
405                     mWaitingForRemote = true;
406                 }
407                 try {
408                     Log.v(TAG, "Set header items for " + fileInfo.mFileName);
409                     request.setHeader(HeaderSet.NAME, fileInfo.mFileName);
410                     request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype);
411 
412                     applyRemoteDeviceQuirks(request, mInfo.mDestination, fileInfo.mFileName);
413                     Constants.updateShareStatus(
414                             mContext1, mInfo.mId, BluetoothShare.STATUS_RUNNING);
415 
416                     request.setHeader(HeaderSet.LENGTH, fileInfo.mLength);
417 
418                     Log.v(TAG, "put headerset for " + fileInfo.mFileName);
419                     putOperation = (ClientOperation) mCs.put(request);
420                 } catch (IllegalArgumentException e) {
421                     ContentProfileErrorReportUtils.report(
422                             BluetoothProfile.OPP,
423                             BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
424                             BluetoothStatsLog
425                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
426                             8);
427                     status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
428                     Constants.updateShareStatus(mContext1, mInfo.mId, status);
429 
430                     Log.e(TAG, "Error setting header items for request: " + e);
431                     error = true;
432                 } catch (IOException e) {
433                     ContentProfileErrorReportUtils.report(
434                             BluetoothProfile.OPP,
435                             BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
436                             BluetoothStatsLog
437                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
438                             9);
439                     status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
440                     Constants.updateShareStatus(mContext1, mInfo.mId, status);
441 
442                     Log.e(TAG, "Error when put HeaderSet ");
443                     error = true;
444                 }
445                 synchronized (this) {
446                     mWaitingForRemote = false;
447                 }
448 
449                 if (!error) {
450                     try {
451                         Log.v(TAG, "openOutputStream " + fileInfo.mFileName);
452                         outputStream = putOperation.openOutputStream();
453                         inputStream = putOperation.openInputStream();
454                     } catch (IOException e) {
455                         ContentProfileErrorReportUtils.report(
456                                 BluetoothProfile.OPP,
457                                 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
458                                 BluetoothStatsLog
459                                         .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
460                                 10);
461                         status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
462                         Constants.updateShareStatus(mContext1, mInfo.mId, status);
463                         Log.e(TAG, "Error when openOutputStream");
464                         error = true;
465                     }
466                 }
467                 if (!error) {
468                     updateValues = new ContentValues();
469                     updateValues.put(BluetoothShare.CURRENT_BYTES, 0);
470                     updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING);
471                     mContext1.getContentResolver().update(contentUri, updateValues, null, null);
472                 }
473 
474                 if (!error) {
475                     int readLength = 0;
476                     long percent = 0;
477                     long prevPercent = 0;
478                     boolean okToProceed = false;
479                     long timestamp = 0;
480                     long currentTime = 0;
481                     long prevTimestamp = SystemClock.elapsedRealtime();
482                     int outputBufferSize = putOperation.getMaxPacketSize();
483                     byte[] buffer = new byte[outputBufferSize];
484                     BufferedInputStream a = new BufferedInputStream(fileInfo.mInputStream, 0x4000);
485 
486                     if (!mInterrupted && (position != fileInfo.mLength)) {
487                         readLength = readFully(a, buffer, outputBufferSize);
488 
489                         mCallbackHandler.sendMessageDelayed(
490                                 mCallbackHandler.obtainMessage(
491                                         BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
492                                 BluetoothOppObexSession.SESSION_TIMEOUT);
493                         synchronized (this) {
494                             mWaitingForRemote = true;
495                         }
496 
497                         // first packet will block here
498                         outputStream.write(buffer, 0, readLength);
499 
500                         position += readLength;
501 
502                         if (position == fileInfo.mLength) {
503                             // if file length is smaller than buffer size, only one packet
504                             // so block point is here
505                             outputStream.close();
506                             outputStream = null;
507                         }
508 
509                         /* check remote accept or reject */
510                         responseCode = putOperation.getResponseCode();
511 
512                         mCallbackHandler.removeMessages(
513                                 BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
514                         synchronized (this) {
515                             mWaitingForRemote = false;
516                         }
517 
518                         if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE
519                                 || responseCode == ResponseCodes.OBEX_HTTP_OK) {
520                             Log.v(TAG, "Remote accept");
521                             okToProceed = true;
522                             updateValues = new ContentValues();
523                             updateValues.put(BluetoothShare.CURRENT_BYTES, position);
524                             mContext1
525                                     .getContentResolver()
526                                     .update(contentUri, updateValues, null, null);
527                             mNumFilesAttemptedToSend++;
528                         } else {
529                             Log.i(TAG, "Remote reject, Response code is " + responseCode);
530                         }
531                     }
532 
533                     while (!mInterrupted && okToProceed && (position < fileInfo.mLength)) {
534                         timestamp = SystemClock.elapsedRealtime();
535 
536                         readLength = a.read(buffer, 0, outputBufferSize);
537                         outputStream.write(buffer, 0, readLength);
538 
539                         /* check remote abort */
540                         responseCode = putOperation.getResponseCode();
541                         Log.v(TAG, "Response code is " + responseCode);
542                         if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE
543                                 && responseCode != ResponseCodes.OBEX_HTTP_OK) {
544                             /* abort happens */
545                             okToProceed = false;
546                         } else {
547                             position += readLength;
548                             currentTime = SystemClock.elapsedRealtime();
549                             Log.v(
550                                     TAG,
551                                     "Sending file position = "
552                                             + position
553                                             + " readLength "
554                                             + readLength
555                                             + " bytes took "
556                                             + (currentTime - timestamp)
557                                             + " ms");
558                             // Update the Progress Bar only if there is change in percentage
559                             // or once per a period to notify NFC of this transfer is still alive
560                             percent = position * 100 / fileInfo.mLength;
561                             if (percent > prevPercent
562                                     || currentTime - prevTimestamp > Constants.NFC_ALIVE_CHECK_MS) {
563                                 updateValues = new ContentValues();
564                                 updateValues.put(BluetoothShare.CURRENT_BYTES, position);
565                                 mContext1
566                                         .getContentResolver()
567                                         .update(contentUri, updateValues, null, null);
568                                 prevPercent = percent;
569                                 prevTimestamp = currentTime;
570                             }
571                         }
572                     }
573 
574                     if (responseCode == ResponseCodes.OBEX_HTTP_FORBIDDEN
575                             || responseCode == ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) {
576                         Log.i(
577                                 TAG,
578                                 "Remote reject file "
579                                         + fileInfo.mFileName
580                                         + " length "
581                                         + fileInfo.mLength);
582                         status = BluetoothShare.STATUS_FORBIDDEN;
583                     } else if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) {
584                         Log.i(TAG, "Remote reject file type " + fileInfo.mMimetype);
585                         status = BluetoothShare.STATUS_NOT_ACCEPTABLE;
586                     } else if (!mInterrupted && position == fileInfo.mLength) {
587                         Log.i(
588                                 TAG,
589                                 "SendFile finished send out file "
590                                         + fileInfo.mFileName
591                                         + " length "
592                                         + fileInfo.mLength);
593                     } else {
594                         error = true;
595                         status = BluetoothShare.STATUS_CANCELED;
596                         putOperation.abort();
597                         /* interrupted */
598                         Log.i(
599                                 TAG,
600                                 "SendFile interrupted when send out file "
601                                         + fileInfo.mFileName
602                                         + " at "
603                                         + position
604                                         + " of "
605                                         + fileInfo.mLength);
606                     }
607                 }
608             } catch (IOException e) {
609                 ContentProfileErrorReportUtils.report(
610                         BluetoothProfile.OPP,
611                         BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
612                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
613                         11);
614                 handleSendException(e.toString());
615             } catch (NullPointerException e) {
616                 ContentProfileErrorReportUtils.report(
617                         BluetoothProfile.OPP,
618                         BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
619                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
620                         12);
621                 handleSendException(e.toString());
622             } catch (IndexOutOfBoundsException e) {
623                 ContentProfileErrorReportUtils.report(
624                         BluetoothProfile.OPP,
625                         BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
626                         BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
627                         13);
628                 handleSendException(e.toString());
629             } finally {
630                 try {
631                     if (outputStream != null) {
632                         outputStream.close();
633                     }
634                 } catch (IOException e) {
635                     ContentProfileErrorReportUtils.report(
636                             BluetoothProfile.OPP,
637                             BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
638                             BluetoothStatsLog
639                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
640                             14);
641                     Log.e(TAG, "Error when closing output stream after send");
642                 }
643 
644                 // Close InputStream and remove SendFileInfo from map
645                 BluetoothOppUtility.closeSendFileInfo(mInfo.mUri);
646                 try {
647                     if (!error) {
648                         responseCode = putOperation.getResponseCode();
649                         if (responseCode != -1) {
650                             Log.v(TAG, "Get response code " + responseCode);
651                             if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
652                                 Log.i(TAG, "Response error code is " + responseCode);
653                                 status = BluetoothShare.STATUS_UNHANDLED_OBEX_CODE;
654                                 if (responseCode == ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE) {
655                                     status = BluetoothShare.STATUS_NOT_ACCEPTABLE;
656                                 }
657                                 if (responseCode == ResponseCodes.OBEX_HTTP_FORBIDDEN
658                                         || responseCode == ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE) {
659                                     status = BluetoothShare.STATUS_FORBIDDEN;
660                                 }
661                             }
662                         } else {
663                             // responseCode is -1, which means connection error
664                             status = BluetoothShare.STATUS_CONNECTION_ERROR;
665                         }
666                     }
667 
668                     Constants.updateShareStatus(mContext1, mInfo.mId, status);
669 
670                     if (inputStream != null) {
671                         inputStream.close();
672                     }
673                     if (putOperation != null) {
674                         putOperation.close();
675                     }
676                 } catch (IOException e) {
677                     ContentProfileErrorReportUtils.report(
678                             BluetoothProfile.OPP,
679                             BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
680                             BluetoothStatsLog
681                                     .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
682                             15);
683                     Log.e(TAG, "Error when closing stream after send");
684 
685                     // Socket has been closed due to the response timeout in the framework,
686                     // mark the transfer as failure.
687                     if (position != fileInfo.mLength) {
688                         status = BluetoothShare.STATUS_FORBIDDEN;
689                         Constants.updateShareStatus(mContext1, mInfo.mId, status);
690                     }
691                 }
692             }
693             BluetoothOppUtility.cancelNotification(mContext);
694             return status;
695         }
696 
handleSendException(String exception)697         private void handleSendException(String exception) {
698             Log.e(TAG, "Error when sending file: " + exception);
699             // Update interrupted outbound content resolver entry when
700             // error during transfer.
701             Constants.updateShareStatus(
702                     mContext1, mInfo.mId, BluetoothShare.STATUS_OBEX_DATA_ERROR);
703             mCallbackHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
704         }
705 
706         @Override
interrupt()707         public void interrupt() {
708             super.interrupt();
709             synchronized (this) {
710                 if (mWaitingForRemote) {
711                     Log.v(TAG, "Interrupted when waitingForRemote");
712                     try {
713                         mTransport1.close();
714                     } catch (IOException e) {
715                         ContentProfileErrorReportUtils.report(
716                                 BluetoothProfile.OPP,
717                                 BluetoothProtoEnums.BLUETOOTH_OPP_OBEX_CLIENT_SESSION,
718                                 BluetoothStatsLog
719                                         .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION,
720                                 16);
721                         Log.e(TAG, "mTransport.close error");
722                     }
723                     Message msg = Message.obtain(mCallbackHandler);
724                     msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED;
725                     if (mInfo != null) {
726                         msg.obj = mInfo;
727                     }
728                     msg.sendToTarget();
729                 }
730             }
731         }
732     }
733 
applyRemoteDeviceQuirks(HeaderSet request, String address, String filename)734     public static void applyRemoteDeviceQuirks(HeaderSet request, String address, String filename) {
735         if (address == null) {
736             return;
737         }
738         if (address.startsWith("00:04:48")) {
739             // Poloroid Pogo
740             // Rejects filenames with more than one '.'. Rename to '_'.
741             // for example: 'a.b.jpg' -> 'a_b.jpg'
742             //              'abc.jpg' NOT CHANGED
743             char[] c = filename.toCharArray();
744             boolean firstDot = true;
745             boolean modified = false;
746             for (int i = c.length - 1; i >= 0; i--) {
747                 if (c[i] == '.') {
748                     if (!firstDot) {
749                         modified = true;
750                         c[i] = '_';
751                     }
752                     firstDot = false;
753                 }
754             }
755 
756             if (modified) {
757                 String newFilename = new String(c);
758                 request.setHeader(HeaderSet.NAME, newFilename);
759                 Log.i(
760                         TAG,
761                         "Sending file \""
762                                 + filename
763                                 + "\" as \""
764                                 + newFilename
765                                 + "\" to workaround Poloroid filename quirk");
766             }
767         }
768     }
769 
770     @Override
unblock()771     public void unblock() {
772         // Not used for client case
773     }
774 }
775