Home | History | Annotate | Download | only in opp
      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      */
    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         }
    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 
    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 
    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 
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    318     public void enableBluetooth() {
    319         if (mAdapter != null) {
    320             mAdapter.enable();
    321         }
    322     }
    323 
    324     /**
    325      * Disable Bluetooth hardware.
    326      */
    327     public void disableBluetooth() {
    328         if (mAdapter != null) {
    329             mAdapter.disable();
    330         }
    331     }
    332 
    333     /**
    334      * Get device name per bluetooth address.
    335      */
    336     public String getDeviceName(BluetoothDevice device) {
    337         String deviceName = null;
    338 
    339         if (device != null) {
    340             deviceName = device.getAliasName();
    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 
    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      */
    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 
    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
    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          */
    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          */
    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 
    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