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