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