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