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