• 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.content.ContentResolver;
38 import android.content.ContentValues;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.SharedPreferences;
42 import android.net.Uri;
43 import android.os.Process;
44 import android.os.SystemClock;
45 import android.text.TextUtils;
46 import android.util.Log;
47 import android.util.Pair;
48 
49 import com.android.bluetooth.BluetoothMethodProxy;
50 import com.android.bluetooth.R;
51 import com.android.internal.annotations.VisibleForTesting;
52 
53 import java.util.ArrayList;
54 import java.util.Iterator;
55 import java.util.List;
56 
57 /**
58  * This class provides a simplified interface on top of other Bluetooth service
59  * layer components; Also it handles some Opp application level variables. It's
60  * a singleton got from BluetoothOppManager.getInstance(context);
61  */
62 public class BluetoothOppManager {
63     private static final String TAG = "BluetoothOppManager";
64     private static final boolean V = Constants.VERBOSE;
65 
66     @VisibleForTesting
67     static BluetoothOppManager sInstance;
68 
69     /** Used when obtaining a reference to the singleton instance. */
70     private static final Object INSTANCE_LOCK = new Object();
71 
72     private boolean mInitialized;
73 
74     private Context mContext;
75 
76     private BluetoothAdapter mAdapter;
77 
78     @VisibleForTesting
79     String mMimeTypeOfSendingFile;
80 
81     @VisibleForTesting
82     String mUriOfSendingFile;
83 
84     @VisibleForTesting
85     String mMimeTypeOfSendingFiles;
86 
87     @VisibleForTesting
88     ArrayList<Uri> mUrisOfSendingFiles;
89 
90     private boolean mIsHandoverInitiated;
91 
92     @VisibleForTesting
93     static final String OPP_PREFERENCE_FILE = "OPPMGR";
94 
95     private static final String SENDING_FLAG = "SENDINGFLAG";
96 
97     private static final String MIME_TYPE = "MIMETYPE";
98 
99     private static final String FILE_URI = "FILE_URI";
100 
101     private static final String MIME_TYPE_MULTIPLE = "MIMETYPE_MULTIPLE";
102 
103     private static final String FILE_URIS = "FILE_URIS";
104 
105     private static final String MULTIPLE_FLAG = "MULTIPLE_FLAG";
106 
107     private static final String ARRAYLIST_ITEM_SEPERATOR = ";";
108 
109     @VisibleForTesting
110     static final int ALLOWED_INSERT_SHARE_THREAD_NUMBER = 3;
111 
112     // used to judge if need continue sending process after received a
113     // ENABLED_ACTION
114     public boolean mSendingFlag;
115 
116     public boolean mMultipleFlag;
117 
118     private int mFileNumInBatch;
119 
120     private int mInsertShareThreadNum = 0;
121 
122     // A list of devices that may send files over OPP to this device
123     // without user confirmation. Used for connection handover from forex NFC.
124     private List<Pair<String, Long>> mAcceptlist = new ArrayList<Pair<String, Long>>();
125 
126     // The time for which the acceptlist entries remain valid.
127     private static final int ACCEPTLIST_DURATION_MS = 15000;
128 
129     /**
130      * Get singleton instance.
131      */
getInstance(Context context)132     public static BluetoothOppManager getInstance(Context context) {
133         synchronized (INSTANCE_LOCK) {
134             if (sInstance == null) {
135                 sInstance = new BluetoothOppManager();
136             }
137             sInstance.init(context);
138 
139             return sInstance;
140         }
141     }
142 
143     /**
144      * Set Singleton instance. Intended for testing purpose
145      */
146     @VisibleForTesting
setInstance(BluetoothOppManager instance)147     static void setInstance(BluetoothOppManager instance) {
148         sInstance = instance;
149     }
150 
151     /**
152      * init
153      */
init(Context context)154     private boolean init(Context context) {
155         if (mInitialized) {
156             return true;
157         }
158         mInitialized = true;
159 
160         mContext = context;
161 
162         mAdapter = BluetoothAdapter.getDefaultAdapter();
163         if (mAdapter == null) {
164             if (V) {
165                 Log.v(TAG, "BLUETOOTH_SERVICE is not started! ");
166             }
167         }
168 
169         // Restore data from preference
170         restoreApplicationData();
171 
172         return true;
173     }
174 
175 
cleanupAcceptlist()176     private void cleanupAcceptlist() {
177         // Removes expired entries
178         long curTime = SystemClock.elapsedRealtime();
179         for (Iterator<Pair<String, Long>> iter = mAcceptlist.iterator(); iter.hasNext(); ) {
180             Pair<String, Long> entry = iter.next();
181             if (curTime - entry.second > ACCEPTLIST_DURATION_MS) {
182                 if (V) {
183                     Log.v(TAG, "Cleaning out acceptlist entry " + entry.first);
184                 }
185                 iter.remove();
186             }
187         }
188     }
189 
addToAcceptlist(String address)190     public synchronized void addToAcceptlist(String address) {
191         if (address == null) {
192             return;
193         }
194         // Remove any existing entries
195         for (Iterator<Pair<String, Long>> iter = mAcceptlist.iterator(); iter.hasNext(); ) {
196             Pair<String, Long> entry = iter.next();
197             if (entry.first.equals(address)) {
198                 iter.remove();
199             }
200         }
201         mAcceptlist.add(new Pair<String, Long>(address, SystemClock.elapsedRealtime()));
202     }
203 
isAcceptlisted(String address)204     public synchronized boolean isAcceptlisted(String address) {
205         cleanupAcceptlist();
206         for (Pair<String, Long> entry : mAcceptlist) {
207             if (entry.first.equals(address)) {
208                 return true;
209             }
210         }
211         return false;
212     }
213 
214     /**
215      * Restore data from preference
216      */
restoreApplicationData()217     private void restoreApplicationData() {
218         SharedPreferences settings = mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0);
219 
220         // All member vars are not initialized till now
221         mSendingFlag = settings.getBoolean(SENDING_FLAG, false);
222         mMimeTypeOfSendingFile = settings.getString(MIME_TYPE, null);
223         mUriOfSendingFile = settings.getString(FILE_URI, null);
224         mMimeTypeOfSendingFiles = settings.getString(MIME_TYPE_MULTIPLE, null);
225         mMultipleFlag = settings.getBoolean(MULTIPLE_FLAG, false);
226 
227         if (V) {
228             Log.v(TAG, "restoreApplicationData! " + mSendingFlag + mMultipleFlag
229                     + mMimeTypeOfSendingFile + mUriOfSendingFile);
230         }
231 
232         String strUris = settings.getString(FILE_URIS, null);
233         mUrisOfSendingFiles = new ArrayList<Uri>();
234         if (strUris != null) {
235             String[] splitUri = strUris.split(ARRAYLIST_ITEM_SEPERATOR);
236             for (int i = 0; i < splitUri.length; i++) {
237                 mUrisOfSendingFiles.add(Uri.parse(splitUri[i]));
238                 if (V) {
239                     Log.v(TAG, "Uri in batch:  " + Uri.parse(splitUri[i]));
240                 }
241             }
242         }
243 
244         mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0).edit().clear().apply();
245     }
246 
247     /**
248      * Save application data to preference, need restore these data when service restart
249      */
storeApplicationData()250     private void storeApplicationData() {
251         SharedPreferences.Editor editor =
252                 mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0).edit();
253         editor.putBoolean(SENDING_FLAG, mSendingFlag);
254         editor.putBoolean(MULTIPLE_FLAG, mMultipleFlag);
255         if (mMultipleFlag) {
256             editor.putString(MIME_TYPE_MULTIPLE, mMimeTypeOfSendingFiles);
257             StringBuilder sb = new StringBuilder();
258             for (int i = 0, count = mUrisOfSendingFiles.size(); i < count; i++) {
259                 Uri uriContent = mUrisOfSendingFiles.get(i);
260                 sb.append(uriContent);
261                 sb.append(ARRAYLIST_ITEM_SEPERATOR);
262             }
263             String strUris = sb.toString();
264             editor.putString(FILE_URIS, strUris);
265 
266             editor.remove(MIME_TYPE);
267             editor.remove(FILE_URI);
268         } else {
269             editor.putString(MIME_TYPE, mMimeTypeOfSendingFile);
270             editor.putString(FILE_URI, mUriOfSendingFile);
271 
272             editor.remove(MIME_TYPE_MULTIPLE);
273             editor.remove(FILE_URIS);
274         }
275         editor.apply();
276         if (V) {
277             Log.v(TAG, "Application data stored to SharedPreference! ");
278         }
279     }
280 
saveSendingFileInfo(String mimeType, String uriString, boolean isHandover, boolean fromExternal)281     public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover,
282             boolean fromExternal) throws IllegalArgumentException {
283         synchronized (BluetoothOppManager.this) {
284             mMultipleFlag = false;
285             mMimeTypeOfSendingFile = mimeType;
286             mIsHandoverInitiated = isHandover;
287             Uri uri = Uri.parse(uriString);
288             BluetoothOppSendFileInfo sendFileInfo =
289                     BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType,
290                     fromExternal);
291             uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
292             BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
293             mUriOfSendingFile = uri.toString();
294             storeApplicationData();
295         }
296     }
297 
saveSendingFileInfo(String mimeType, ArrayList<Uri> uris, boolean isHandover, boolean fromExternal)298     public void saveSendingFileInfo(String mimeType, ArrayList<Uri> uris, boolean isHandover,
299             boolean fromExternal) throws IllegalArgumentException {
300         synchronized (BluetoothOppManager.this) {
301             mMultipleFlag = true;
302             mMimeTypeOfSendingFiles = mimeType;
303             mUrisOfSendingFiles = new ArrayList<Uri>();
304             mIsHandoverInitiated = isHandover;
305             for (Uri uri : uris) {
306                 BluetoothOppSendFileInfo sendFileInfo =
307                         BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType,
308                         fromExternal);
309                 uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
310                 mUrisOfSendingFiles.add(uri);
311                 BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
312             }
313             storeApplicationData();
314         }
315     }
316 
317     /**
318      * Get the current status of Bluetooth hardware.
319      * @return true if Bluetooth enabled, false otherwise.
320      */
isEnabled()321     public boolean isEnabled() {
322         if (mAdapter != null) {
323             return BluetoothMethodProxy.getInstance().bluetoothAdapterIsEnabled(mAdapter);
324         } else {
325             if (V) {
326                 Log.v(TAG, "BLUETOOTH_SERVICE is not available! ");
327             }
328             return false;
329         }
330     }
331 
332     /**
333      * Enable Bluetooth hardware.
334      */
enableBluetooth()335     public void enableBluetooth() {
336         if (mAdapter != null) {
337             mAdapter.enable();
338         }
339     }
340 
341     /**
342      * Disable Bluetooth hardware.
343      */
disableBluetooth()344     public void disableBluetooth() {
345         if (mAdapter != null) {
346             mAdapter.disable();
347         }
348     }
349 
350     /**
351      * Get device name per bluetooth address.
352      */
getDeviceName(BluetoothDevice device)353     public String getDeviceName(BluetoothDevice device) {
354         String deviceName = null;
355 
356         if (device != null) {
357             deviceName = device.getAlias();
358             if (deviceName == null) {
359                 deviceName = BluetoothOppPreference.getInstance(mContext).getName(device);
360             }
361         }
362 
363         if (deviceName == null) {
364             deviceName = mContext.getString(R.string.unknown_device);
365         }
366 
367         return deviceName;
368     }
369 
getBatchSize()370     public int getBatchSize() {
371         synchronized (BluetoothOppManager.this) {
372             return mFileNumInBatch;
373         }
374     }
375 
376     /**
377      * Fork a thread to insert share info to db.
378      */
startTransfer(BluetoothDevice device)379     public void startTransfer(BluetoothDevice device) {
380         if (V) {
381             Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
382         }
383         InsertShareInfoThread insertThread;
384         synchronized (BluetoothOppManager.this) {
385             if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {
386                 Log.e(TAG, "Too many shares user triggered concurrently!");
387 
388                 // Notice user
389                 Intent in = new Intent(mContext, BluetoothOppBtErrorActivity.class);
390                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
391                 in.putExtra("title", mContext.getString(R.string.enabling_progress_title));
392                 in.putExtra("content", mContext.getString(R.string.ErrorTooManyRequests));
393                 mContext.startActivity(in);
394 
395                 return;
396             }
397             insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,
398                     mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,
399                     mIsHandoverInitiated);
400             if (mMultipleFlag) {
401                 mFileNumInBatch = mUrisOfSendingFiles.size();
402             }
403         }
404 
405         insertThread.start();
406     }
407 
408     /**
409      * Thread to insert share info to db. In multiple files (say 100 files)
410      * share case, the inserting share info to db operation would be a time
411      * consuming operation, so need a thread to handle it. This thread allows
412      * multiple instances to support below case: User select multiple files to
413      * share to one device (say device 1), and then right away share to second
414      * device (device 2), we need insert all these share info to db.
415      */
416     private class InsertShareInfoThread extends Thread {
417         private final BluetoothDevice mRemoteDevice;
418 
419         private final String mTypeOfSingleFile;
420 
421         private final String mUri;
422 
423         private final String mTypeOfMultipleFiles;
424 
425         private final ArrayList<Uri> mUris;
426 
427         private final boolean mIsMultiple;
428 
429         private final boolean mIsHandoverInitiated;
430 
InsertShareInfoThread(BluetoothDevice device, boolean multiple, String typeOfSingleFile, String uri, String typeOfMultipleFiles, ArrayList<Uri> uris, boolean handoverInitiated)431         InsertShareInfoThread(BluetoothDevice device, boolean multiple, String typeOfSingleFile,
432                 String uri, String typeOfMultipleFiles, ArrayList<Uri> uris,
433                 boolean handoverInitiated) {
434             super("Insert ShareInfo Thread");
435             this.mRemoteDevice = device;
436             this.mIsMultiple = multiple;
437             this.mTypeOfSingleFile = typeOfSingleFile;
438             this.mUri = uri;
439             this.mTypeOfMultipleFiles = typeOfMultipleFiles;
440             this.mUris = uris;
441             this.mIsHandoverInitiated = handoverInitiated;
442 
443             synchronized (BluetoothOppManager.this) {
444                 mInsertShareThreadNum++;
445             }
446 
447             if (V) {
448                 Log.v(TAG, "Thread id is: " + this.getId());
449             }
450         }
451 
452         @Override
run()453         public void run() {
454             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
455             if (mRemoteDevice == null) {
456                 Log.e(TAG, "Target bt device is null!");
457                 return;
458             }
459             if (mIsMultiple) {
460                 insertMultipleShare();
461             } else {
462                 insertSingleShare();
463             }
464             synchronized (BluetoothOppManager.this) {
465                 mInsertShareThreadNum--;
466             }
467         }
468 
469         /**
470          * Insert multiple sending sessions to db, only used by Opp application.
471          */
insertMultipleShare()472         private void insertMultipleShare() {
473             int count = mUris.size();
474             Long ts = System.currentTimeMillis();
475             for (int i = 0; i < count; i++) {
476                 Uri fileUri = mUris.get(i);
477                 ContentValues values = new ContentValues();
478                 values.put(BluetoothShare.URI, fileUri.toString());
479                 ContentResolver contentResolver = mContext.getContentResolver();
480                 fileUri = BluetoothOppUtility.originalUri(fileUri);
481                 String contentType = contentResolver.getType(fileUri);
482                 if (V) {
483                     Log.v(TAG, "Got mimetype: " + contentType + "  Got uri: " + fileUri);
484                 }
485                 if (TextUtils.isEmpty(contentType)) {
486                     contentType = mTypeOfMultipleFiles;
487                 }
488 
489                 values.put(BluetoothShare.MIMETYPE, contentType);
490                 values.put(BluetoothShare.DESTINATION, mRemoteDevice.getIdentityAddress());
491                 values.put(BluetoothShare.TIMESTAMP, ts);
492                 if (mIsHandoverInitiated) {
493                     values.put(BluetoothShare.USER_CONFIRMATION,
494                             BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
495                 }
496                 final Uri contentUri = BluetoothMethodProxy.getInstance().contentResolverInsert(
497                         mContext.getContentResolver(), BluetoothShare.CONTENT_URI, values);
498                 if (V) {
499                     Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: " + getDeviceName(
500                             mRemoteDevice));
501                 }
502             }
503         }
504 
505         /**
506          * Insert single sending session to db, only used by Opp application.
507          */
insertSingleShare()508         private void insertSingleShare() {
509             ContentValues values = new ContentValues();
510             values.put(BluetoothShare.URI, mUri);
511             values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile);
512             values.put(BluetoothShare.DESTINATION, mRemoteDevice.getIdentityAddress());
513             if (mIsHandoverInitiated) {
514                 values.put(BluetoothShare.USER_CONFIRMATION,
515                         BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
516             }
517             final Uri contentUri = BluetoothMethodProxy.getInstance().contentResolverInsert(
518                     mContext.getContentResolver(), BluetoothShare.CONTENT_URI, values);
519             if (V) {
520                 Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: " + getDeviceName(
521                         mRemoteDevice));
522             }
523         }
524     }
525 
cleanUpSendingFileInfo()526     void cleanUpSendingFileInfo() {
527         synchronized (BluetoothOppManager.this) {
528             if (V) {
529                 Log.v(TAG, "cleanUpSendingFileInfo: mMultipleFlag = " + mMultipleFlag);
530             }
531             if (!mMultipleFlag && (mUriOfSendingFile != null)) {
532                 Uri uri = Uri.parse(mUriOfSendingFile);
533                 BluetoothOppUtility.closeSendFileInfo(uri);
534             } else if (mUrisOfSendingFiles != null) {
535                 for (Uri uri : mUrisOfSendingFiles) {
536                     BluetoothOppUtility.closeSendFileInfo(uri);
537                 }
538             }
539         }
540     }
541 }
542