BluetoothOppManager.java revision 1bd017d12cf16ecd52fb486722e300790bddeefc
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 33package com.android.bluetooth.opp; 34 35import com.android.bluetooth.R; 36 37import android.bluetooth.BluetoothAdapter; 38import android.bluetooth.BluetoothDevice; 39import android.content.ContentResolver; 40import android.content.ContentValues; 41import android.content.Context; 42import android.content.Intent; 43import android.content.SharedPreferences; 44import android.net.Uri; 45import android.os.Process; 46import android.os.SystemClock; 47import android.text.TextUtils; 48import android.util.Log; 49import android.util.Pair; 50 51import java.util.ArrayList; 52import java.util.Iterator; 53import 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 */ 60public 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 */ 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 */ 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 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 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 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 */ 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 */ 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 249 public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover, 250 boolean fromExternal) throws IllegalArgumentException { 251 synchronized (BluetoothOppManager.this) { 252 mMultipleFlag = false; 253 mMimeTypeOfSendingFile = mimeType; 254 mUriOfSendingFile = uriString; 255 mIsHandoverInitiated = isHandover; 256 Uri uri = Uri.parse(uriString); 257 BluetoothOppUtility.putSendFileInfo( 258 uri, BluetoothOppSendFileInfo.generateFileInfo( 259 mContext, uri, mimeType, fromExternal)); 260 storeApplicationData(); 261 } 262 } 263 264 public void saveSendingFileInfo(String mimeType, ArrayList<Uri> uris, boolean isHandover, 265 boolean fromExternal) throws IllegalArgumentException { 266 synchronized (BluetoothOppManager.this) { 267 mMultipleFlag = true; 268 mMimeTypeOfSendingFiles = mimeType; 269 mUrisOfSendingFiles = uris; 270 mIsHandoverInitiated = isHandover; 271 for (Uri uri : uris) { 272 BluetoothOppUtility.putSendFileInfo( 273 uri, BluetoothOppSendFileInfo.generateFileInfo( 274 mContext, uri, mimeType, fromExternal)); 275 } 276 storeApplicationData(); 277 } 278 } 279 280 /** 281 * Get the current status of Bluetooth hardware. 282 * @return true if Bluetooth enabled, false otherwise. 283 */ 284 public boolean isEnabled() { 285 if (mAdapter != null) { 286 return mAdapter.isEnabled(); 287 } else { 288 if (V) Log.v(TAG, "BLUETOOTH_SERVICE is not available! "); 289 return false; 290 } 291 } 292 293 /** 294 * Enable Bluetooth hardware. 295 */ 296 public void enableBluetooth() { 297 if (mAdapter != null) { 298 mAdapter.enable(); 299 } 300 } 301 302 /** 303 * Disable Bluetooth hardware. 304 */ 305 public void disableBluetooth() { 306 if (mAdapter != null) { 307 mAdapter.disable(); 308 } 309 } 310 311 /** 312 * Get device name per bluetooth address. 313 */ 314 public String getDeviceName(BluetoothDevice device) { 315 String deviceName; 316 317 deviceName = BluetoothOppPreference.getInstance(mContext).getName(device); 318 319 if (deviceName == null && mAdapter != null) { 320 deviceName = device.getName(); 321 } 322 323 if (deviceName == null) { 324 deviceName = mContext.getString(R.string.unknown_device); 325 } 326 327 return deviceName; 328 } 329 330 public int getBatchSize() { 331 synchronized (BluetoothOppManager.this) { 332 return mfileNumInBatch; 333 } 334 } 335 336 /** 337 * Fork a thread to insert share info to db. 338 */ 339 public void startTransfer(BluetoothDevice device) { 340 if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum); 341 InsertShareInfoThread insertThread; 342 synchronized (BluetoothOppManager.this) { 343 if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) { 344 Log.e(TAG, "Too many shares user triggered concurrently!"); 345 346 // Notice user 347 Intent in = new Intent(mContext, BluetoothOppBtErrorActivity.class); 348 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 349 in.putExtra("title", mContext.getString(R.string.enabling_progress_title)); 350 in.putExtra("content", mContext.getString(R.string.ErrorTooManyRequests)); 351 mContext.startActivity(in); 352 353 return; 354 } 355 insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile, 356 mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles, 357 mIsHandoverInitiated); 358 if (mMultipleFlag) { 359 mfileNumInBatch = mUrisOfSendingFiles.size(); 360 } 361 } 362 363 insertThread.start(); 364 } 365 366 /** 367 * Thread to insert share info to db. In multiple files (say 100 files) 368 * share case, the inserting share info to db operation would be a time 369 * consuming operation, so need a thread to handle it. This thread allows 370 * multiple instances to support below case: User select multiple files to 371 * share to one device (say device 1), and then right away share to second 372 * device (device 2), we need insert all these share info to db. 373 */ 374 private class InsertShareInfoThread extends Thread { 375 private final BluetoothDevice mRemoteDevice; 376 377 private final String mTypeOfSingleFile; 378 379 private final String mUri; 380 381 private final String mTypeOfMultipleFiles; 382 383 private final ArrayList<Uri> mUris; 384 385 private final boolean mIsMultiple; 386 387 private final boolean mIsHandoverInitiated; 388 389 InsertShareInfoThread(BluetoothDevice device, boolean multiple, 390 String typeOfSingleFile, String uri, String typeOfMultipleFiles, 391 ArrayList<Uri> uris, boolean handoverInitiated) { 392 super("Insert ShareInfo Thread"); 393 this.mRemoteDevice = device; 394 this.mIsMultiple = multiple; 395 this.mTypeOfSingleFile = typeOfSingleFile; 396 this.mUri = uri; 397 this.mTypeOfMultipleFiles = typeOfMultipleFiles; 398 this.mUris = uris; 399 this.mIsHandoverInitiated = handoverInitiated; 400 401 synchronized (BluetoothOppManager.this) { 402 mInsertShareThreadNum++; 403 } 404 405 if (V) Log.v(TAG, "Thread id is: " + this.getId()); 406 } 407 408 @Override 409 public void run() { 410 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 411 if (mRemoteDevice == null) { 412 Log.e(TAG, "Target bt device is null!"); 413 return; 414 } 415 if (mIsMultiple) { 416 insertMultipleShare(); 417 } else { 418 insertSingleShare(); 419 } 420 synchronized (BluetoothOppManager.this) { 421 mInsertShareThreadNum--; 422 } 423 } 424 425 /** 426 * Insert multiple sending sessions to db, only used by Opp application. 427 */ 428 private void insertMultipleShare() { 429 int count = mUris.size(); 430 Long ts = System.currentTimeMillis(); 431 for (int i = 0; i < count; i++) { 432 Uri fileUri = mUris.get(i); 433 ContentResolver contentResolver = mContext.getContentResolver(); 434 String contentType = contentResolver.getType(fileUri); 435 if (V) Log.v(TAG, "Got mimetype: " + contentType + " Got uri: " + fileUri); 436 if (TextUtils.isEmpty(contentType)) { 437 contentType = mTypeOfMultipleFiles; 438 } 439 440 ContentValues values = new ContentValues(); 441 values.put(BluetoothShare.URI, fileUri.toString()); 442 values.put(BluetoothShare.MIMETYPE, contentType); 443 values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress()); 444 values.put(BluetoothShare.TIMESTAMP, ts); 445 if (mIsHandoverInitiated) { 446 values.put(BluetoothShare.USER_CONFIRMATION, 447 BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED); 448 } 449 final Uri contentUri = mContext.getContentResolver().insert( 450 BluetoothShare.CONTENT_URI, values); 451 if (V) Log.v(TAG, "Insert contentUri: " + contentUri + " to device: " 452 + getDeviceName(mRemoteDevice)); 453 } 454 } 455 456 /** 457 * Insert single sending session to db, only used by Opp application. 458 */ 459 private void insertSingleShare() { 460 ContentValues values = new ContentValues(); 461 values.put(BluetoothShare.URI, mUri); 462 values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile); 463 values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress()); 464 if (mIsHandoverInitiated) { 465 values.put(BluetoothShare.USER_CONFIRMATION, 466 BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED); 467 } 468 final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, 469 values); 470 if (V) Log.v(TAG, "Insert contentUri: " + contentUri + " to device: " 471 + getDeviceName(mRemoteDevice)); 472 } 473 } 474 475 void cleanUpSendingFileInfo() { 476 synchronized (BluetoothOppManager.this) { 477 if (V) Log.v(TAG, "cleanUpSendingFileInfo: mMultipleFlag = " + mMultipleFlag); 478 if (!mMultipleFlag && (mUriOfSendingFile != null)) { 479 Uri uri = Uri.parse(mUriOfSendingFile); 480 BluetoothOppUtility.closeSendFileInfo(uri); 481 } else if (mUrisOfSendingFiles != null) { 482 for (Uri uri : mUrisOfSendingFiles) { 483 BluetoothOppUtility.closeSendFileInfo(uri); 484 } 485 } 486 } 487 } 488} 489