• 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.BluetoothAdapter;
36 import android.bluetooth.BluetoothDevice;
37 import android.bluetooth.BluetoothDevicePicker;
38 import android.bluetooth.BluetoothSocket;
39 import android.content.BroadcastReceiver;
40 import android.content.ContentResolver;
41 import android.content.ContentValues;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.IntentFilter;
45 import android.database.CharArrayBuffer;
46 import android.database.ContentObserver;
47 import android.database.Cursor;
48 import android.media.MediaScannerConnection;
49 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
50 import android.net.Uri;
51 import android.os.Binder;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.os.Process;
55 import android.sysprop.BluetoothProperties;
56 import android.util.Log;
57 
58 import com.android.bluetooth.BluetoothObexTransport;
59 import com.android.bluetooth.IObexConnectionHandler;
60 import com.android.bluetooth.ObexServerSockets;
61 import com.android.bluetooth.btservice.AdapterService;
62 import com.android.bluetooth.btservice.ProfileService;
63 import com.android.bluetooth.sdp.SdpManager;
64 import com.android.internal.annotations.VisibleForTesting;
65 import com.android.obex.ObexTransport;
66 
67 import java.io.IOException;
68 import java.text.SimpleDateFormat;
69 import java.util.ArrayList;
70 import java.util.Date;
71 import java.util.Locale;
72 
73 /**
74  * Performs the background Bluetooth OPP transfer. It also starts thread to
75  * accept incoming OPP connection.
76  */
77 
78 public class BluetoothOppService extends ProfileService implements IObexConnectionHandler {
79     private static final boolean D = Constants.DEBUG;
80     private static final boolean V = Constants.VERBOSE;
81 
82     /**
83      * Owned providers and activities
84      */
85     private static final String OPP_PROVIDER =
86             BluetoothOppProvider.class.getCanonicalName();
87     private static final String OPP_FILE_PROVIDER =
88             BluetoothOppFileProvider.class.getCanonicalName();
89     private static final String INCOMING_FILE_CONFIRM_ACTIVITY =
90             BluetoothOppIncomingFileConfirmActivity.class.getCanonicalName();
91     private static final String TRANSFER_ACTIVITY =
92             BluetoothOppTransferActivity.class.getCanonicalName();
93     private static final String TRANSFER_HISTORY_ACTIVITY =
94             BluetoothOppTransferHistory.class.getCanonicalName();
95     // Normally we would dynamically define and create these but they need to be manifest receivers
96     // because they rely on explicit intents. Explicit intents don't work with dynamic receivers.
97     private static final String OPP_RECEIVER =
98             BluetoothOppReceiver.class.getCanonicalName();
99     private static final String OPP_HANDOFF_RECEIVER =
100             BluetoothOppHandoverReceiver.class.getCanonicalName();
101 
102     private static final byte[] SUPPORTED_OPP_FORMAT = {
103             0x01 /* vCard 2.1 */,
104             0x02 /* vCard 3.0 */,
105             0x03 /* vCal 1.0 */,
106             0x04 /* iCal 2.0 */,
107             (byte) 0xFF /* Any type of object */
108     };
109 
110     private class BluetoothShareContentObserver extends ContentObserver {
111 
BluetoothShareContentObserver()112         BluetoothShareContentObserver() {
113             super(new Handler());
114         }
115 
116         @Override
onChange(boolean selfChange)117         public void onChange(boolean selfChange) {
118             if (V) {
119                 Log.v(TAG, "ContentObserver received notification");
120             }
121             updateFromProvider();
122         }
123     }
124 
125     private static final String TAG = "BtOppService";
126 
127     /** Observer to get notified when the content observer's data changes */
128     private BluetoothShareContentObserver mObserver;
129 
130     /** Class to handle Notification Manager updates */
131     private BluetoothOppNotification mNotifier;
132 
133     private boolean mPendingUpdate;
134 
135     private UpdateThread mUpdateThread;
136 
137     private boolean mUpdateThreadRunning;
138 
139     private ArrayList<BluetoothOppShareInfo> mShares;
140 
141     private ArrayList<BluetoothOppBatch> mBatches;
142 
143     private BluetoothOppTransfer mTransfer;
144 
145     private BluetoothOppTransfer mServerTransfer;
146 
147     private int mBatchId;
148 
149     /**
150      * Array used when extracting strings from content provider
151      */
152     private CharArrayBuffer mOldChars;
153     /**
154      * Array used when extracting strings from content provider
155      */
156     private CharArrayBuffer mNewChars;
157 
158     private boolean mListenStarted;
159 
160     private boolean mMediaScanInProgress;
161 
162     private int mIncomingRetries;
163 
164     private ObexTransport mPendingConnection;
165 
166     private int mOppSdpHandle = -1;
167 
168     boolean mAcceptNewConnections;
169 
170     private AdapterService mAdapterService;
171 
172     private static final String INVISIBLE =
173             BluetoothShare.VISIBILITY + "=" + BluetoothShare.VISIBILITY_HIDDEN;
174 
175     private static final String WHERE_INBOUND_SUCCESS =
176             BluetoothShare.DIRECTION + "=" + BluetoothShare.DIRECTION_INBOUND + " AND "
177                     + BluetoothShare.STATUS + "=" + BluetoothShare.STATUS_SUCCESS + " AND "
178                     + INVISIBLE;
179 
180     private static final String WHERE_CONFIRM_PENDING_INBOUND =
181             BluetoothShare.DIRECTION + "=" + BluetoothShare.DIRECTION_INBOUND + " AND "
182                     + BluetoothShare.USER_CONFIRMATION + "="
183                     + BluetoothShare.USER_CONFIRMATION_PENDING;
184 
185     private static final String WHERE_INVISIBLE_UNCONFIRMED =
186             "(" + BluetoothShare.STATUS + " > " + BluetoothShare.STATUS_SUCCESS + " AND "
187                     + INVISIBLE + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";
188 
189     private static BluetoothOppService sBluetoothOppService;
190 
191     /*
192      * TODO No support for queue incoming from multiple devices.
193      * Make an array list of server session to support receiving queue from
194      * multiple devices
195      */
196     private BluetoothOppObexServerSession mServerSession;
197 
isEnabled()198     public static boolean isEnabled() {
199         return BluetoothProperties.isProfileOppEnabled().orElse(false);
200     }
201 
202     @Override
initBinder()203     protected IProfileServiceBinder initBinder() {
204         return new OppBinder(this);
205     }
206 
207     private static class OppBinder extends Binder implements IProfileServiceBinder {
208 
OppBinder(BluetoothOppService service)209         OppBinder(BluetoothOppService service) {
210         }
211 
212         @Override
cleanup()213         public void cleanup() {
214         }
215     }
216 
217     @Override
create()218     protected void create() {
219         if (V) {
220             Log.v(TAG, "onCreate");
221         }
222         mShares = new ArrayList();
223         mBatches = new ArrayList();
224         mBatchId = 1;
225 
226         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
227         registerReceiver(mBluetoothReceiver, filter);
228 
229         if (V) {
230             BluetoothOppPreference preference = BluetoothOppPreference.getInstance(this);
231             if (preference != null) {
232                 preference.dump();
233             } else {
234                 Log.w(TAG, "BluetoothOppPreference.getInstance returned null.");
235             }
236         }
237     }
238 
239     @Override
start()240     public boolean start() {
241         if (V) {
242             Log.v(TAG, "start()");
243         }
244 
245         setComponentAvailable(OPP_PROVIDER, true);
246         setComponentAvailable(OPP_FILE_PROVIDER, true);
247         setComponentAvailable(INCOMING_FILE_CONFIRM_ACTIVITY, true);
248         setComponentAvailable(TRANSFER_ACTIVITY, true);
249         setComponentAvailable(TRANSFER_HISTORY_ACTIVITY, true);
250         setComponentAvailable(OPP_RECEIVER, true);
251         setComponentAvailable(OPP_HANDOFF_RECEIVER, true);
252 
253         final ContentResolver contentResolver = getContentResolver();
254         new Thread("trimDatabase") {
255             @Override
256             public void run() {
257                 trimDatabase(contentResolver);
258             }
259         }.start();
260 
261         mAdapterService = AdapterService.getAdapterService();
262         mObserver = new BluetoothShareContentObserver();
263         getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
264         mNotifier = new BluetoothOppNotification(this);
265         mNotifier.mNotificationMgr.cancelAll();
266         mNotifier.updateNotification();
267         updateFromProvider();
268         setBluetoothOppService(this);
269         mAdapterService.notifyActivityAttributionInfo(
270                 getAttributionSource(),
271                 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
272         return true;
273     }
274 
275     @Override
stop()276     public boolean stop() {
277         if (sBluetoothOppService == null) {
278             Log.w(TAG, "stop() called before start()");
279             return true;
280         }
281         mAdapterService.notifyActivityAttributionInfo(
282                 getAttributionSource(),
283                 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
284         setBluetoothOppService(null);
285         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
286 
287         setComponentAvailable(OPP_PROVIDER, false);
288         setComponentAvailable(OPP_FILE_PROVIDER, false);
289         setComponentAvailable(INCOMING_FILE_CONFIRM_ACTIVITY, false);
290         setComponentAvailable(TRANSFER_ACTIVITY, false);
291         setComponentAvailable(TRANSFER_HISTORY_ACTIVITY, false);
292         setComponentAvailable(OPP_RECEIVER, false);
293         setComponentAvailable(OPP_HANDOFF_RECEIVER, false);
294 
295         return true;
296     }
297 
startListener()298     private void startListener() {
299         if (!mListenStarted) {
300             if (mAdapterService.isEnabled()) {
301                 if (V) {
302                     Log.v(TAG, "Starting RfcommListener");
303                 }
304                 mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
305                 mListenStarted = true;
306             }
307         }
308     }
309 
310     @Override
dump(StringBuilder sb)311     public void dump(StringBuilder sb) {
312         super.dump(sb);
313         if (mShares.size() > 0) {
314             println(sb, "Shares:");
315             for (BluetoothOppShareInfo info : mShares) {
316                 String dir = info.mDirection == BluetoothShare.DIRECTION_OUTBOUND ? " -> " : " <- ";
317                 SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
318                 Date date = new Date(info.mTimestamp);
319                 println(sb, "  " + format.format(date) + dir + info.mCurrentBytes + "/"
320                         + info.mTotalBytes);
321             }
322         }
323     }
324 
325     /**
326      * Get the current instance of {@link BluetoothOppService}
327      *
328      * @return current instance of {@link BluetoothOppService}
329      */
330     @VisibleForTesting
getBluetoothOppService()331     public static synchronized BluetoothOppService getBluetoothOppService() {
332         if (sBluetoothOppService == null) {
333             Log.w(TAG, "getBluetoothOppService(): service is null");
334             return null;
335         }
336         if (!sBluetoothOppService.isAvailable()) {
337             Log.w(TAG, "getBluetoothOppService(): service is not available");
338             return null;
339         }
340         return sBluetoothOppService;
341     }
342 
setBluetoothOppService(BluetoothOppService instance)343     private static synchronized void setBluetoothOppService(BluetoothOppService instance) {
344         if (D) {
345             Log.d(TAG, "setBluetoothOppService(): set to: " + instance);
346         }
347         sBluetoothOppService = instance;
348     }
349 
350     private static final int START_LISTENER = 1;
351 
352     private static final int MEDIA_SCANNED = 2;
353 
354     private static final int MEDIA_SCANNED_FAILED = 3;
355 
356     private static final int MSG_INCOMING_CONNECTION_RETRY = 4;
357 
358     private static final int MSG_INCOMING_BTOPP_CONNECTION = 100;
359 
360     private static final int STOP_LISTENER = 200;
361 
362     private Handler mHandler = new Handler() {
363         @Override
364         public void handleMessage(Message msg) {
365             switch (msg.what) {
366                 case STOP_LISTENER:
367                     stopListeners();
368                     mListenStarted = false;
369                     //Stop Active INBOUND Transfer
370                     if (mServerTransfer != null) {
371                         mServerTransfer.onBatchCanceled();
372                         mServerTransfer = null;
373                     }
374                     //Stop Active OUTBOUND Transfer
375                     if (mTransfer != null) {
376                         mTransfer.onBatchCanceled();
377                         mTransfer = null;
378                     }
379                     unregisterReceivers();
380                     synchronized (BluetoothOppService.this) {
381                         if (mUpdateThread != null) {
382                             mUpdateThread.interrupt();
383                         }
384                     }
385                     while (mUpdateThread != null && mUpdateThreadRunning) {
386                         try {
387                             Thread.sleep(50);
388                         } catch (Exception e) {
389                             Log.e(TAG, "Thread sleep", e);
390                         }
391                     }
392                     synchronized (BluetoothOppService.this) {
393                         if (mUpdateThread != null) {
394                             try {
395                                 mUpdateThread.join();
396                             } catch (InterruptedException e) {
397                                 Log.e(TAG, "Interrupted", e);
398                             }
399                             mUpdateThread = null;
400                         }
401                     }
402 
403                     mNotifier.cancelNotifications();
404                     break;
405                 case START_LISTENER:
406                     if (mAdapterService.isEnabled()) {
407                         startSocketListener();
408                     }
409                     break;
410                 case MEDIA_SCANNED:
411                     if (V) {
412                         Log.v(TAG, "Update mInfo.id " + msg.arg1 + " for data uri= "
413                                 + msg.obj.toString());
414                     }
415                     ContentValues updateValues = new ContentValues();
416                     Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + msg.arg1);
417                     updateValues.put(Constants.MEDIA_SCANNED, Constants.MEDIA_SCANNED_SCANNED_OK);
418                     updateValues.put(BluetoothShare.URI, msg.obj.toString()); // update
419                     updateValues.put(BluetoothShare.MIMETYPE,
420                             getContentResolver().getType(Uri.parse(msg.obj.toString())));
421                     getContentResolver().update(contentUri, updateValues, null, null);
422                     synchronized (BluetoothOppService.this) {
423                         mMediaScanInProgress = false;
424                     }
425                     break;
426                 case MEDIA_SCANNED_FAILED:
427                     Log.v(TAG, "Update mInfo.id " + msg.arg1 + " for MEDIA_SCANNED_FAILED");
428                     ContentValues updateValues1 = new ContentValues();
429                     Uri contentUri1 = Uri.parse(BluetoothShare.CONTENT_URI + "/" + msg.arg1);
430                     updateValues1.put(Constants.MEDIA_SCANNED,
431                             Constants.MEDIA_SCANNED_SCANNED_FAILED);
432                     getContentResolver().update(contentUri1, updateValues1, null, null);
433                     synchronized (BluetoothOppService.this) {
434                         mMediaScanInProgress = false;
435                     }
436                     break;
437                 case MSG_INCOMING_BTOPP_CONNECTION:
438                     if (D) {
439                         Log.d(TAG, "Get incoming connection");
440                     }
441                     ObexTransport transport = (ObexTransport) msg.obj;
442 
443                     /*
444                      * Strategy for incoming connections:
445                      * 1. If there is no ongoing transfer, no on-hold connection, start it
446                      * 2. If there is ongoing transfer, hold it for 20 seconds(1 seconds * 20 times)
447                      * 3. If there is on-hold connection, reject directly
448                      */
449                     if (mBatches.size() == 0 && mPendingConnection == null) {
450                         Log.i(TAG, "Start Obex Server");
451                         createServerSession(transport);
452                     } else {
453                         if (mPendingConnection != null) {
454                             Log.w(TAG, "OPP busy! Reject connection");
455                             try {
456                                 transport.close();
457                             } catch (IOException e) {
458                                 Log.e(TAG, "close tranport error");
459                             }
460                         } else {
461                             Log.i(TAG, "OPP busy! Retry after 1 second");
462                             mIncomingRetries = mIncomingRetries + 1;
463                             mPendingConnection = transport;
464                             Message msg1 = Message.obtain(mHandler);
465                             msg1.what = MSG_INCOMING_CONNECTION_RETRY;
466                             mHandler.sendMessageDelayed(msg1, 1000);
467                         }
468                     }
469                     break;
470                 case MSG_INCOMING_CONNECTION_RETRY:
471                     if (mBatches.size() == 0) {
472                         Log.i(TAG, "Start Obex Server");
473                         createServerSession(mPendingConnection);
474                         mIncomingRetries = 0;
475                         mPendingConnection = null;
476                     } else {
477                         if (mIncomingRetries == 20) {
478                             Log.w(TAG, "Retried 20 seconds, reject connection");
479                             try {
480                                 mPendingConnection.close();
481                             } catch (IOException e) {
482                                 Log.e(TAG, "close tranport error");
483                             }
484                             if (mServerSocket != null) {
485                                 acceptNewConnections();
486                             }
487                             mIncomingRetries = 0;
488                             mPendingConnection = null;
489                         } else {
490                             Log.i(TAG, "OPP busy! Retry after 1 second");
491                             mIncomingRetries = mIncomingRetries + 1;
492                             Message msg2 = Message.obtain(mHandler);
493                             msg2.what = MSG_INCOMING_CONNECTION_RETRY;
494                             mHandler.sendMessageDelayed(msg2, 1000);
495                         }
496                     }
497                     break;
498             }
499         }
500     };
501 
502     private ObexServerSockets mServerSocket;
503 
startSocketListener()504     private void startSocketListener() {
505         if (D) {
506             Log.d(TAG, "start Socket Listeners");
507         }
508         stopListeners();
509         mServerSocket = ObexServerSockets.createInsecure(this);
510         acceptNewConnections();
511         SdpManager sdpManager = SdpManager.getDefaultManager();
512         if (sdpManager == null || mServerSocket == null) {
513             Log.e(TAG, "ERROR:serversocket object is NULL  sdp manager :" + sdpManager
514                     + " mServerSocket:" + mServerSocket);
515             return;
516         }
517         mOppSdpHandle =
518                 sdpManager.createOppOpsRecord("OBEX Object Push", mServerSocket.getRfcommChannel(),
519                         mServerSocket.getL2capPsm(), 0x0102, SUPPORTED_OPP_FORMAT);
520         if (D) {
521             Log.d(TAG, "mOppSdpHandle :" + mOppSdpHandle);
522         }
523     }
524 
525     @Override
cleanup()526     protected void cleanup() {
527         if (V) {
528             Log.v(TAG, "onDestroy");
529         }
530         stopListeners();
531         if (mBatches != null) {
532             mBatches.clear();
533         }
534         if (mShares != null) {
535             mShares.clear();
536         }
537         if (mHandler != null) {
538             mHandler.removeCallbacksAndMessages(null);
539         }
540     }
541 
unregisterReceivers()542     private void unregisterReceivers() {
543         try {
544             if (mObserver != null) {
545                 getContentResolver().unregisterContentObserver(mObserver);
546                 mObserver = null;
547             }
548             unregisterReceiver(mBluetoothReceiver);
549         } catch (IllegalArgumentException e) {
550             Log.w(TAG, "unregisterReceivers " + e.toString());
551         }
552     }
553 
554     /* suppose we auto accept an incoming OPUSH connection */
createServerSession(ObexTransport transport)555     private void createServerSession(ObexTransport transport) {
556         mServerSession = new BluetoothOppObexServerSession(this, transport, this);
557         mServerSession.preStart();
558         if (D) {
559             Log.d(TAG, "Get ServerSession " + mServerSession.toString() + " for incoming connection"
560                     + transport.toString());
561         }
562     }
563 
564     private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
565         @Override
566         public void onReceive(Context context, Intent intent) {
567             String action = intent.getAction();
568 
569             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
570                 switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
571                     case BluetoothAdapter.STATE_ON:
572                         if (V) {
573                             Log.v(TAG, "Bluetooth state changed: STATE_ON");
574                         }
575                         startListener();
576                         // If this is within a sending process, continue the handle
577                         // logic to display device picker dialog.
578                         synchronized (this) {
579                             if (BluetoothOppManager.getInstance(context).mSendingFlag) {
580                                 // reset the flags
581                                 BluetoothOppManager.getInstance(context).mSendingFlag = false;
582 
583                                 Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
584                                 in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
585                                 in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
586                                         BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
587                                 in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
588                                         getPackageName());
589                                 in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
590                                         BluetoothOppReceiver.class.getName());
591 
592                                 in1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
593                                 context.startActivity(in1);
594                             }
595                         }
596 
597                         break;
598                     case BluetoothAdapter.STATE_TURNING_OFF:
599                         if (V) {
600                             Log.v(TAG, "Bluetooth state changed: STATE_TURNING_OFF");
601                         }
602                         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
603                         break;
604                 }
605             }
606         }
607     };
608 
updateFromProvider()609     private void updateFromProvider() {
610         synchronized (BluetoothOppService.this) {
611             mPendingUpdate = true;
612             if (mUpdateThread == null) {
613                 mUpdateThread = new UpdateThread();
614                 mUpdateThread.start();
615                 mUpdateThreadRunning = true;
616             }
617         }
618     }
619 
620     private class UpdateThread extends Thread {
621         private boolean mIsInterrupted;
622 
UpdateThread()623         UpdateThread() {
624             super("Bluetooth Share Service");
625             mIsInterrupted = false;
626         }
627 
628         @Override
interrupt()629         public void interrupt() {
630             mIsInterrupted = true;
631             if (D) {
632                 Log.d(TAG, "OPP UpdateThread interrupted ");
633             }
634             super.interrupt();
635         }
636 
637 
638         @Override
run()639         public void run() {
640             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
641 
642             while (!mIsInterrupted) {
643                 synchronized (BluetoothOppService.this) {
644                     if (mUpdateThread != this) {
645                         mUpdateThreadRunning = false;
646                         throw new IllegalStateException(
647                                 "multiple UpdateThreads in BluetoothOppService");
648                     }
649                     if (V) {
650                         Log.v(TAG, "pendingUpdate is " + mPendingUpdate + " sListenStarted is "
651                                 + mListenStarted + " isInterrupted :" + mIsInterrupted);
652                     }
653                     if (!mPendingUpdate) {
654                         mUpdateThread = null;
655                         mUpdateThreadRunning = false;
656                         return;
657                     }
658                     mPendingUpdate = false;
659                 }
660                 Cursor cursor =
661                         getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null,
662                                 BluetoothShare._ID);
663 
664                 if (cursor == null) {
665                     mUpdateThreadRunning = false;
666                     return;
667                 }
668 
669                 cursor.moveToFirst();
670 
671                 int arrayPos = 0;
672 
673                 boolean isAfterLast = cursor.isAfterLast();
674 
675                 int idColumn = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
676                 /*
677                  * Walk the cursor and the local array to keep them in sync. The
678                  * key to the algorithm is that the ids are unique and sorted
679                  * both in the cursor and in the array, so that they can be
680                  * processed in order in both sources at the same time: at each
681                  * step, both sources point to the lowest id that hasn't been
682                  * processed from that source, and the algorithm processes the
683                  * lowest id from those two possibilities. At each step: -If the
684                  * array contains an entry that's not in the cursor, remove the
685                  * entry, move to next entry in the array. -If the array
686                  * contains an entry that's in the cursor, nothing to do, move
687                  * to next cursor row and next array entry. -If the cursor
688                  * contains an entry that's not in the array, insert a new entry
689                  * in the array, move to next cursor row and next array entry.
690                  */
691                 while (!isAfterLast || arrayPos < mShares.size() && mListenStarted) {
692                     if (isAfterLast) {
693                         // We're beyond the end of the cursor but there's still some
694                         // stuff in the local array, which can only be junk
695                         if (mShares.size() != 0) {
696                             if (V) {
697                                 Log.v(TAG, "Array update: trimming " + mShares.get(arrayPos).mId
698                                         + " @ " + arrayPos);
699                             }
700                         }
701 
702                         deleteShare(arrayPos); // this advances in the array
703                     } else {
704                         int id = cursor.getInt(idColumn);
705 
706                         if (arrayPos == mShares.size()) {
707                             insertShare(cursor, arrayPos);
708                             if (V) {
709                                 Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
710                             }
711                             ++arrayPos;
712                             cursor.moveToNext();
713                             isAfterLast = cursor.isAfterLast();
714                         } else {
715                             int arrayId = 0;
716                             if (mShares.size() != 0) {
717                                 arrayId = mShares.get(arrayPos).mId;
718                             }
719 
720                             if (arrayId < id) {
721                                 if (V) {
722                                     Log.v(TAG,
723                                             "Array update: removing " + arrayId + " @ " + arrayPos);
724                                 }
725                                 deleteShare(arrayPos);
726                             } else if (arrayId == id) {
727                                 // This cursor row already exists in the stored array.
728                                 updateShare(cursor, arrayPos);
729                                 scanFileIfNeeded(arrayPos);
730                                 ++arrayPos;
731                                 cursor.moveToNext();
732                                 isAfterLast = cursor.isAfterLast();
733                             } else {
734                                 // This cursor entry didn't exist in the stored
735                                 // array
736                                 if (V) {
737                                     Log.v(TAG, "Array update: appending " + id + " @ " + arrayPos);
738                                 }
739                                 insertShare(cursor, arrayPos);
740 
741                                 ++arrayPos;
742                                 cursor.moveToNext();
743                                 isAfterLast = cursor.isAfterLast();
744                             }
745                         }
746                     }
747                 }
748 
749                 mNotifier.updateNotification();
750 
751                 cursor.close();
752             }
753 
754             mUpdateThreadRunning = false;
755         }
756     }
757 
insertShare(Cursor cursor, int arrayPos)758     private void insertShare(Cursor cursor, int arrayPos) {
759         String uriString = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
760         Uri uri;
761         if (uriString != null) {
762             uri = Uri.parse(uriString);
763             Log.d(TAG, "insertShare parsed URI: " + uri);
764         } else {
765             uri = null;
766             Log.e(TAG, "insertShare found null URI at cursor!");
767         }
768         BluetoothOppShareInfo info = new BluetoothOppShareInfo(
769                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)), uri,
770                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)),
771                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)),
772                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE)),
773                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)),
774                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION)),
775                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY)),
776                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION)),
777                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)),
778                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)),
779                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)),
780                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)),
781                 cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
782                         != Constants.MEDIA_SCANNED_NOT_SCANNED);
783 
784         if (V) {
785             Log.v(TAG, "Service adding new entry");
786             Log.v(TAG, "ID      : " + info.mId);
787             // Log.v(TAG, "URI     : " + ((info.mUri != null) ? "yes" : "no"));
788             Log.v(TAG, "URI     : " + info.mUri);
789             Log.v(TAG, "HINT    : " + info.mHint);
790             Log.v(TAG, "FILENAME: " + info.mFilename);
791             Log.v(TAG, "MIMETYPE: " + info.mMimetype);
792             Log.v(TAG, "DIRECTION: " + info.mDirection);
793             Log.v(TAG, "DESTINAT: " + info.mDestination);
794             Log.v(TAG, "VISIBILI: " + info.mVisibility);
795             Log.v(TAG, "CONFIRM : " + info.mConfirm);
796             Log.v(TAG, "STATUS  : " + info.mStatus);
797             Log.v(TAG, "TOTAL   : " + info.mTotalBytes);
798             Log.v(TAG, "CURRENT : " + info.mCurrentBytes);
799             Log.v(TAG, "TIMESTAMP : " + info.mTimestamp);
800             Log.v(TAG, "SCANNED : " + info.mMediaScanned);
801         }
802 
803         mShares.add(arrayPos, info);
804 
805         /* Mark the info as failed if it's in invalid status */
806         if (info.isObsolete()) {
807             Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_UNKNOWN_ERROR);
808         }
809         /*
810          * Add info into a batch. The logic is
811          * 1) Only add valid and readyToStart info
812          * 2) If there is no batch, create a batch and insert this transfer into batch,
813          * then run the batch
814          * 3) If there is existing batch and timestamp match, insert transfer into batch
815          * 4) If there is existing batch and timestamp does not match, create a new batch and
816          * put in queue
817          */
818 
819         if (info.isReadyToStart()) {
820             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
821                 /* check if the file exists */
822                 BluetoothOppSendFileInfo sendFileInfo =
823                         BluetoothOppUtility.getSendFileInfo(info.mUri);
824                 if (sendFileInfo == null || sendFileInfo.mInputStream == null) {
825                     Log.e(TAG, "Can't open file for OUTBOUND info " + info.mId);
826                     Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_BAD_REQUEST);
827                     BluetoothOppUtility.closeSendFileInfo(info.mUri);
828                     return;
829                 }
830             }
831             if (mBatches.size() == 0) {
832                 BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
833                 newBatch.mId = mBatchId;
834                 mBatchId++;
835                 mBatches.add(newBatch);
836                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
837                     if (V) {
838                         Log.v(TAG,
839                                 "Service create new Batch " + newBatch.mId + " for OUTBOUND info "
840                                         + info.mId);
841                     }
842                     mTransfer = new BluetoothOppTransfer(this, newBatch);
843                 } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
844                     if (V) {
845                         Log.v(TAG, "Service create new Batch " + newBatch.mId + " for INBOUND info "
846                                 + info.mId);
847                     }
848                     mServerTransfer = new BluetoothOppTransfer(this, newBatch, mServerSession);
849                 }
850 
851                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {
852                     if (V) {
853                         Log.v(TAG, "Service start transfer new Batch " + newBatch.mId + " for info "
854                                 + info.mId);
855                     }
856                     mTransfer.start();
857                 } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND
858                         && mServerTransfer != null) {
859                     if (V) {
860                         Log.v(TAG, "Service start server transfer new Batch " + newBatch.mId
861                                 + " for info " + info.mId);
862                     }
863                     mServerTransfer.start();
864                 }
865 
866             } else {
867                 int i = findBatchWithTimeStamp(info.mTimestamp);
868                 if (i != -1) {
869                     if (V) {
870                         Log.v(TAG, "Service add info " + info.mId + " to existing batch " + mBatches
871                                 .get(i).mId);
872                     }
873                     mBatches.get(i).addShare(info);
874                 } else {
875                     // There is ongoing batch
876                     BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
877                     newBatch.mId = mBatchId;
878                     mBatchId++;
879                     mBatches.add(newBatch);
880                     if (V) {
881                         Log.v(TAG,
882                                 "Service add new Batch " + newBatch.mId + " for info " + info.mId);
883                     }
884                 }
885             }
886         }
887     }
888 
updateShare(Cursor cursor, int arrayPos)889     private void updateShare(Cursor cursor, int arrayPos) {
890         BluetoothOppShareInfo info = mShares.get(arrayPos);
891         int statusColumn = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
892 
893         info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
894         if (info.mUri != null) {
895             String uriString = stringFromCursor(info.mUri.toString(), cursor, BluetoothShare.URI);
896             if (uriString != null) {
897                 info.mUri = Uri.parse(uriString);
898             }
899         } else {
900             Log.w(TAG, "updateShare() called for ID " + info.mId + " with null URI");
901         }
902         info.mHint = stringFromCursor(info.mHint, cursor, BluetoothShare.FILENAME_HINT);
903         info.mFilename = stringFromCursor(info.mFilename, cursor, BluetoothShare._DATA);
904         info.mMimetype = stringFromCursor(info.mMimetype, cursor, BluetoothShare.MIMETYPE);
905         info.mDirection = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION));
906         info.mDestination = stringFromCursor(info.mDestination, cursor, BluetoothShare.DESTINATION);
907         int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY));
908 
909         boolean confirmUpdated = false;
910         int newConfirm =
911                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
912 
913         if (info.mVisibility == BluetoothShare.VISIBILITY_VISIBLE
914                 && newVisibility != BluetoothShare.VISIBILITY_VISIBLE && (
915                 BluetoothShare.isStatusCompleted(info.mStatus)
916                         || newConfirm == BluetoothShare.USER_CONFIRMATION_PENDING)) {
917             mNotifier.mNotificationMgr.cancel(info.mId);
918         }
919 
920         info.mVisibility = newVisibility;
921 
922         if (info.mConfirm == BluetoothShare.USER_CONFIRMATION_PENDING
923                 && newConfirm != BluetoothShare.USER_CONFIRMATION_PENDING) {
924             confirmUpdated = true;
925         }
926         info.mConfirm =
927                 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
928         int newStatus = cursor.getInt(statusColumn);
929 
930         if (BluetoothShare.isStatusCompleted(info.mStatus)) {
931             mNotifier.mNotificationMgr.cancel(info.mId);
932         }
933 
934         info.mStatus = newStatus;
935         info.mTotalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
936         info.mCurrentBytes =
937                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
938         info.mTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
939         info.mMediaScanned = (cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
940                 != Constants.MEDIA_SCANNED_NOT_SCANNED);
941 
942         if (confirmUpdated) {
943             if (V) {
944                 Log.v(TAG, "Service handle info " + info.mId + " confirmation updated");
945             }
946             /* Inbounds transfer user confirmation status changed, update the session server */
947             int i = findBatchWithTimeStamp(info.mTimestamp);
948             if (i != -1) {
949                 BluetoothOppBatch batch = mBatches.get(i);
950                 if (mServerTransfer != null && batch.mId == mServerTransfer.getBatchId()) {
951                     mServerTransfer.confirmStatusChanged();
952                 } //TODO need to think about else
953             }
954         }
955         int i = findBatchWithTimeStamp(info.mTimestamp);
956         if (i != -1) {
957             BluetoothOppBatch batch = mBatches.get(i);
958             if (batch.mStatus == Constants.BATCH_STATUS_FINISHED
959                     || batch.mStatus == Constants.BATCH_STATUS_FAILED) {
960                 if (V) {
961                     Log.v(TAG, "Batch " + batch.mId + " is finished");
962                 }
963                 if (batch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
964                     if (mTransfer == null) {
965                         Log.e(TAG, "Unexpected error! mTransfer is null");
966                     } else if (batch.mId == mTransfer.getBatchId()) {
967                         mTransfer.stop();
968                     } else {
969                         Log.e(TAG, "Unexpected error! batch id " + batch.mId
970                                 + " doesn't match mTransfer id " + mTransfer.getBatchId());
971                     }
972                     mTransfer = null;
973                 } else {
974                     if (mServerTransfer == null) {
975                         Log.e(TAG, "Unexpected error! mServerTransfer is null");
976                     } else if (batch.mId == mServerTransfer.getBatchId()) {
977                         mServerTransfer.stop();
978                     } else {
979                         Log.e(TAG, "Unexpected error! batch id " + batch.mId
980                                 + " doesn't match mServerTransfer id "
981                                 + mServerTransfer.getBatchId());
982                     }
983                     mServerTransfer = null;
984                 }
985                 removeBatch(batch);
986             }
987         }
988     }
989 
990     /**
991      * Removes the local copy of the info about a share.
992      */
deleteShare(int arrayPos)993     private void deleteShare(int arrayPos) {
994         BluetoothOppShareInfo info = mShares.get(arrayPos);
995 
996         /*
997          * Delete arrayPos from a batch. The logic is
998          * 1) Search existing batch for the info
999          * 2) cancel the batch
1000          * 3) If the batch become empty delete the batch
1001          */
1002         int i = findBatchWithTimeStamp(info.mTimestamp);
1003         if (i != -1) {
1004             BluetoothOppBatch batch = mBatches.get(i);
1005             if (batch.hasShare(info)) {
1006                 if (V) {
1007                     Log.v(TAG, "Service cancel batch for share " + info.mId);
1008                 }
1009                 batch.cancelBatch();
1010             }
1011             if (batch.isEmpty()) {
1012                 if (V) {
1013                     Log.v(TAG, "Service remove batch  " + batch.mId);
1014                 }
1015                 removeBatch(batch);
1016             }
1017         }
1018         mShares.remove(arrayPos);
1019     }
1020 
stringFromCursor(String old, Cursor cursor, String column)1021     private String stringFromCursor(String old, Cursor cursor, String column) {
1022         int index = cursor.getColumnIndexOrThrow(column);
1023         if (old == null) {
1024             return cursor.getString(index);
1025         }
1026         if (mNewChars == null) {
1027             mNewChars = new CharArrayBuffer(128);
1028         }
1029         cursor.copyStringToBuffer(index, mNewChars);
1030         int length = mNewChars.sizeCopied;
1031         if (length != old.length()) {
1032             return cursor.getString(index);
1033         }
1034         if (mOldChars == null || mOldChars.sizeCopied < length) {
1035             mOldChars = new CharArrayBuffer(length);
1036         }
1037         char[] oldArray = mOldChars.data;
1038         char[] newArray = mNewChars.data;
1039         old.getChars(0, length, oldArray, 0);
1040         for (int i = length - 1; i >= 0; --i) {
1041             if (oldArray[i] != newArray[i]) {
1042                 return new String(newArray, 0, length);
1043             }
1044         }
1045         return old;
1046     }
1047 
findBatchWithTimeStamp(long timestamp)1048     private int findBatchWithTimeStamp(long timestamp) {
1049         for (int i = mBatches.size() - 1; i >= 0; i--) {
1050             if (mBatches.get(i).mTimestamp == timestamp) {
1051                 return i;
1052             }
1053         }
1054         return -1;
1055     }
1056 
removeBatch(BluetoothOppBatch batch)1057     private void removeBatch(BluetoothOppBatch batch) {
1058         if (V) {
1059             Log.v(TAG, "Remove batch " + batch.mId);
1060         }
1061         mBatches.remove(batch);
1062         if (mBatches.size() > 0) {
1063             for (BluetoothOppBatch nextBatch : mBatches) {
1064                 // we have a running batch
1065                 if (nextBatch.mStatus == Constants.BATCH_STATUS_RUNNING) {
1066                     return;
1067                 } else {
1068                     // just finish a transfer, start pending outbound transfer
1069                     if (nextBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
1070                         if (V) {
1071                             Log.v(TAG, "Start pending outbound batch " + nextBatch.mId);
1072                         }
1073                         mTransfer = new BluetoothOppTransfer(this, nextBatch);
1074                         mTransfer.start();
1075                         return;
1076                     } else if (nextBatch.mDirection == BluetoothShare.DIRECTION_INBOUND
1077                             && mServerSession != null) {
1078                         // have to support pending inbound transfer
1079                         // if an outbound transfer and incoming socket happens together
1080                         if (V) {
1081                             Log.v(TAG, "Start pending inbound batch " + nextBatch.mId);
1082                         }
1083                         mServerTransfer = new BluetoothOppTransfer(this, nextBatch, mServerSession);
1084                         mServerTransfer.start();
1085                         if (nextBatch.getPendingShare() != null
1086                                 && nextBatch.getPendingShare().mConfirm
1087                                 == BluetoothShare.USER_CONFIRMATION_CONFIRMED) {
1088                             mServerTransfer.confirmStatusChanged();
1089                         }
1090                         return;
1091                     }
1092                 }
1093             }
1094         }
1095     }
1096 
scanFileIfNeeded(int arrayPos)1097     private void scanFileIfNeeded(int arrayPos) {
1098         BluetoothOppShareInfo info = mShares.get(arrayPos);
1099         boolean isFileReceived = BluetoothShare.isStatusSuccess(info.mStatus)
1100                 && info.mDirection == BluetoothShare.DIRECTION_INBOUND && !info.mMediaScanned
1101                 && info.mConfirm != BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
1102         if (!isFileReceived) {
1103             return;
1104         }
1105         synchronized (BluetoothOppService.this) {
1106             if (D) {
1107                 Log.d(TAG, "Scanning file " + info.mFilename);
1108             }
1109             if (!mMediaScanInProgress) {
1110                 mMediaScanInProgress = true;
1111                 new MediaScannerNotifier(this, info, mHandler);
1112             }
1113         }
1114     }
1115 
1116     // Run in a background thread at boot.
trimDatabase(ContentResolver contentResolver)1117     private static void trimDatabase(ContentResolver contentResolver) {
1118         // remove the invisible/unconfirmed inbound shares
1119         int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
1120                 null);
1121         if (V) {
1122             Log.v(TAG, "Deleted shares, number = " + delNum);
1123         }
1124 
1125         // Keep the latest inbound and successful shares.
1126         Cursor cursor =
1127                 contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
1128                         WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
1129         if (cursor == null) {
1130             return;
1131         }
1132         int recordNum = cursor.getCount();
1133         if (recordNum > Constants.MAX_RECORDS_IN_DATABASE) {
1134             int numToDelete = recordNum - Constants.MAX_RECORDS_IN_DATABASE;
1135 
1136             if (cursor.moveToPosition(numToDelete)) {
1137                 int columnId = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
1138                 long id = cursor.getLong(columnId);
1139                 delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
1140                         BluetoothShare._ID + " < " + id, null);
1141                 if (V) {
1142                     Log.v(TAG, "Deleted old inbound success share: " + delNum);
1143                 }
1144             }
1145         }
1146         cursor.close();
1147     }
1148 
1149     private static class MediaScannerNotifier implements MediaScannerConnectionClient {
1150 
1151         private MediaScannerConnection mConnection;
1152 
1153         private BluetoothOppShareInfo mInfo;
1154 
1155         private Context mContext;
1156 
1157         private Handler mCallback;
1158 
MediaScannerNotifier(Context context, BluetoothOppShareInfo info, Handler handler)1159         MediaScannerNotifier(Context context, BluetoothOppShareInfo info, Handler handler) {
1160             mContext = context;
1161             mInfo = info;
1162             mCallback = handler;
1163             mConnection = new MediaScannerConnection(mContext, this);
1164             if (V) {
1165                 Log.v(TAG, "Connecting to MediaScannerConnection ");
1166             }
1167             mConnection.connect();
1168         }
1169 
1170         @Override
onMediaScannerConnected()1171         public void onMediaScannerConnected() {
1172             if (V) {
1173                 Log.v(TAG, "MediaScannerConnection onMediaScannerConnected");
1174             }
1175             mConnection.scanFile(mInfo.mFilename, mInfo.mMimetype);
1176         }
1177 
1178         @Override
onScanCompleted(String path, Uri uri)1179         public void onScanCompleted(String path, Uri uri) {
1180             try {
1181                 if (V) {
1182                     Log.v(TAG, "MediaScannerConnection onScanCompleted");
1183                     Log.v(TAG, "MediaScannerConnection path is " + path);
1184                     Log.v(TAG, "MediaScannerConnection Uri is " + uri);
1185                 }
1186                 if (uri != null) {
1187                     Message msg = Message.obtain();
1188                     msg.setTarget(mCallback);
1189                     msg.what = MEDIA_SCANNED;
1190                     msg.arg1 = mInfo.mId;
1191                     msg.obj = uri;
1192                     msg.sendToTarget();
1193                 } else {
1194                     Message msg = Message.obtain();
1195                     msg.setTarget(mCallback);
1196                     msg.what = MEDIA_SCANNED_FAILED;
1197                     msg.arg1 = mInfo.mId;
1198                     msg.sendToTarget();
1199                 }
1200             } catch (NullPointerException ex) {
1201                 Log.v(TAG, "!!!MediaScannerConnection exception: " + ex);
1202             } finally {
1203                 if (V) {
1204                     Log.v(TAG, "MediaScannerConnection disconnect");
1205                 }
1206                 mConnection.disconnect();
1207             }
1208         }
1209     }
1210 
stopListeners()1211     private void stopListeners() {
1212         if (mAdapterService != null && mOppSdpHandle >= 0
1213                 && SdpManager.getDefaultManager() != null) {
1214             if (D) {
1215                 Log.d(TAG, "Removing SDP record mOppSdpHandle :" + mOppSdpHandle);
1216             }
1217             boolean status = SdpManager.getDefaultManager().removeSdpRecord(mOppSdpHandle);
1218             Log.d(TAG, "RemoveSDPrecord returns " + status);
1219             mOppSdpHandle = -1;
1220         }
1221         if (mServerSocket != null) {
1222             mServerSocket.shutdown(false);
1223             mServerSocket = null;
1224         }
1225         if (D) {
1226             Log.d(TAG, "stopListeners: mServerSocket is null");
1227         }
1228     }
1229 
1230     @Override
onConnect(BluetoothDevice device, BluetoothSocket socket)1231     public boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
1232 
1233         if (D) {
1234             Log.d(TAG, " onConnect BluetoothSocket :" + socket + " \n :device :" + device.getIdentityAddress());
1235         }
1236         if (!mAcceptNewConnections) {
1237             Log.d(TAG, " onConnect BluetoothSocket :" + socket + " rejected");
1238             return false;
1239         }
1240         BluetoothObexTransport transport = new BluetoothObexTransport(socket);
1241         Message msg = mHandler.obtainMessage(MSG_INCOMING_BTOPP_CONNECTION);
1242         msg.obj = transport;
1243         msg.sendToTarget();
1244         mAcceptNewConnections = false;
1245         return true;
1246     }
1247 
1248     @Override
onAcceptFailed()1249     public void onAcceptFailed() {
1250         Log.d(TAG, " onAcceptFailed:");
1251         mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
1252     }
1253 
1254     /**
1255      * Set mAcceptNewConnections to true to allow new connections.
1256      */
acceptNewConnections()1257     void acceptNewConnections() {
1258         mAcceptNewConnections = true;
1259     }
1260 }
1261