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