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