BluetoothOppLauncherActivity.java revision fc2ae300d5bf5aadee1120ca1e58b67e7f06875c
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 java.io.File;
38import java.io.FileNotFoundException;
39import java.io.FileOutputStream;
40import java.io.IOException;
41import java.util.ArrayList;
42import java.util.regex.Matcher;
43import java.util.regex.Pattern;
44import java.util.Locale;
45
46import android.app.Activity;
47import android.bluetooth.BluetoothDevicePicker;
48import android.content.Intent;
49import android.content.ContentResolver;
50import android.content.Context;
51import android.net.Uri;
52import android.os.Bundle;
53import android.provider.Settings;
54import android.util.Log;
55import android.util.Patterns;
56
57/**
58 * This class is designed to act as the entry point of handling the share intent
59 * via BT from other APPs. and also make "Bluetooth" available in sharing method
60 * selection dialog.
61 */
62public class BluetoothOppLauncherActivity extends Activity {
63    private static final String TAG = "BluetoothLauncherActivity";
64    private static final boolean D = Constants.DEBUG;
65    private static final boolean V = Constants.VERBOSE;
66
67    // Regex that matches characters that have special meaning in HTML. '<', '>', '&' and
68    // multiple continuous spaces.
69    private static final Pattern PLAIN_TEXT_TO_ESCAPE = Pattern.compile("[<>&]| {2,}|\r?\n");
70
71    @Override
72    public void onCreate(Bundle savedInstanceState) {
73        super.onCreate(savedInstanceState);
74
75        Intent intent = getIntent();
76        String action = intent.getAction();
77
78        if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {
79            //Check if Bluetooth is available in the beginning instead of at the end
80            if (!isBluetoothAllowed()) {
81                Intent in = new Intent(this, BluetoothOppBtErrorActivity.class);
82                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
83                in.putExtra("title", this.getString(R.string.airplane_error_title));
84                in.putExtra("content", this.getString(R.string.airplane_error_msg));
85                startActivity(in);
86                finish();
87                return;
88            }
89
90            /*
91             * Other application is trying to share a file via Bluetooth,
92             * probably Pictures, videos, or vCards. The Intent should contain
93             * an EXTRA_STREAM with the data to attach.
94             */
95            if (action.equals(Intent.ACTION_SEND)) {
96                // TODO: handle type == null case
97                final String type = intent.getType();
98                final Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
99                CharSequence extra_text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
100                // If we get ACTION_SEND intent with EXTRA_STREAM, we'll use the
101                // uri data;
102                // If we get ACTION_SEND intent without EXTRA_STREAM, but with
103                // EXTRA_TEXT, we will try send this TEXT out; Currently in
104                // Browser, share one link goes to this case;
105                if (stream != null && type != null) {
106                    if (V) Log.v(TAG, "Get ACTION_SEND intent: Uri = " + stream + "; mimetype = "
107                                + type);
108                    // Save type/stream, will be used when adding transfer
109                    // session to DB.
110                    Thread t = new Thread(new Runnable() {
111                        public void run() {
112                            BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
113                                .saveSendingFileInfo(type,stream.toString(), false);
114                            //Done getting file info..Launch device picker and finish this activity
115                            launchDevicePicker();
116                            finish();
117                        }
118                    });
119                    t.start();
120                    return;
121                } else if (extra_text != null && type != null) {
122                    if (V) Log.v(TAG, "Get ACTION_SEND intent with Extra_text = "
123                                + extra_text.toString() + "; mimetype = " + type);
124                    final Uri fileUri = creatFileForSharedContent(this, extra_text);
125                    if (fileUri != null) {
126                        Thread t = new Thread(new Runnable() {
127                            public void run() {
128                                BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
129                                    .saveSendingFileInfo(type,fileUri.toString(), false);
130                                //Done getting file info..Launch device picker
131                                //and finish this activity
132                                launchDevicePicker();
133                                finish();
134                            }
135                        });
136                        t.start();
137                        return;
138                    } else {
139                        Log.w(TAG,"Error trying to do set text...File not created!");
140                        finish();
141                        return;
142                    }
143                } else {
144                    Log.e(TAG, "type is null; or sending file URI is null");
145                    finish();
146                    return;
147                }
148            } else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {
149                final String mimeType = intent.getType();
150                final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
151                if (mimeType != null && uris != null) {
152                    if (V) Log.v(TAG, "Get ACTION_SHARE_MULTIPLE intent: uris " + uris + "\n Type= "
153                                + mimeType);
154                    Thread t = new Thread(new Runnable() {
155                        public void run() {
156                            BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
157                                .saveSendingFileInfo(mimeType,uris, false);
158                            //Done getting file info..Launch device picker
159                            //and finish this activity
160                            launchDevicePicker();
161                            finish();
162                        }
163                    });
164                    t.start();
165                    return;
166                } else {
167                    Log.e(TAG, "type is null; or sending files URIs are null");
168                    finish();
169                    return;
170                }
171            }
172        } else {
173            Log.w(TAG, "Unsupported action: " + action);
174            finish();
175        }
176    }
177
178    /**
179     * Turns on Bluetooth if not already on, or launches device picker if Bluetooth is on
180     * @return
181     */
182    private final void launchDevicePicker() {
183        // TODO: In the future, we may send intent to DevicePickerActivity
184        // directly,
185        // and let DevicePickerActivity to handle Bluetooth Enable.
186        if (!BluetoothOppManager.getInstance(this).isEnabled()) {
187            if (V) Log.v(TAG, "Prepare Enable BT!! ");
188            Intent in = new Intent(this, BluetoothOppBtEnableActivity.class);
189            in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
190            startActivity(in);
191        } else {
192            if (V) Log.v(TAG, "BT already enabled!! ");
193            Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
194            in1.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
195            in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
196            in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
197                    BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
198            in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
199                    Constants.THIS_PACKAGE_NAME);
200            in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
201                    BluetoothOppReceiver.class.getName());
202            if (V) {Log.d(TAG,"Launching " +BluetoothDevicePicker.ACTION_LAUNCH );}
203            startActivity(in1);
204        }
205    }
206    /* Returns true if Bluetooth is allowed given current airplane mode settings. */
207    private final boolean isBluetoothAllowed() {
208        final ContentResolver resolver = this.getContentResolver();
209
210        // Check if airplane mode is on
211        final boolean isAirplaneModeOn = Settings.System.getInt(resolver,
212                Settings.System.AIRPLANE_MODE_ON, 0) == 1;
213        if (!isAirplaneModeOn) {
214            return true;
215        }
216
217        // Check if airplane mode matters
218        final String airplaneModeRadios = Settings.System.getString(resolver,
219                Settings.System.AIRPLANE_MODE_RADIOS);
220        final boolean isAirplaneSensitive = airplaneModeRadios == null ? true :
221                airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
222        if (!isAirplaneSensitive) {
223            return true;
224        }
225
226        // Check if Bluetooth may be enabled in airplane mode
227        final String airplaneModeToggleableRadios = Settings.System.getString(resolver,
228                Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
229        final boolean isAirplaneToggleable = airplaneModeToggleableRadios == null ? false :
230                airplaneModeToggleableRadios.contains(Settings.System.RADIO_BLUETOOTH);
231        if (isAirplaneToggleable) {
232            return true;
233        }
234
235        // If we get here we're not allowed to use Bluetooth right now
236        return false;
237    }
238
239    private Uri creatFileForSharedContent(Context context, CharSequence shareContent) {
240        if (shareContent == null) {
241            return null;
242        }
243
244        Uri fileUri = null;
245        FileOutputStream outStream = null;
246        try {
247            String fileName = getString(R.string.bluetooth_share_file_name) + ".html";
248            context.deleteFile(fileName);
249
250            /*
251             * Convert the plain text to HTML
252             */
253            StringBuffer sb = new StringBuffer("<html><head><meta http-equiv=\"Content-Type\""
254                    + " content=\"text/html; charset=UTF-8\"/></head><body>");
255            // Escape any inadvertent HTML in the text message
256            String text = escapeCharacterToDisplay(shareContent.toString());
257
258            // Regex that matches Web URL protocol part as case insensitive.
259            Pattern webUrlProtocol = Pattern.compile("(?i)(http|https)://");
260
261            Pattern pattern = Pattern.compile("("
262                    + Patterns.WEB_URL.pattern() + ")|("
263                    + Patterns.EMAIL_ADDRESS.pattern() + ")|("
264                    + Patterns.PHONE.pattern() + ")");
265            // Find any embedded URL's and linkify
266            Matcher m = pattern.matcher(text);
267            while (m.find()) {
268                String matchStr = m.group();
269                String link = null;
270
271                // Find any embedded URL's and linkify
272                if (Patterns.WEB_URL.matcher(matchStr).matches()) {
273                    Matcher proto = webUrlProtocol.matcher(matchStr);
274                    if (proto.find()) {
275                        // This is work around to force URL protocol part be lower case,
276                        // because WebView could follow only lower case protocol link.
277                        link = proto.group().toLowerCase(Locale.US) +
278                                matchStr.substring(proto.end());
279                    } else {
280                        // Patterns.WEB_URL matches URL without protocol part,
281                        // so added default protocol to link.
282                        link = "http://" + matchStr;
283                    }
284
285                // Find any embedded email address
286                } else if (Patterns.EMAIL_ADDRESS.matcher(matchStr).matches()) {
287                    link = "mailto:" + matchStr;
288
289                // Find any embedded phone numbers and linkify
290                } else if (Patterns.PHONE.matcher(matchStr).matches()) {
291                    link = "tel:" + matchStr;
292                }
293                if (link != null) {
294                    String href = String.format("<a href=\"%s\">%s</a>", link, matchStr);
295                    m.appendReplacement(sb, href);
296                }
297            }
298            m.appendTail(sb);
299            sb.append("</body></html>");
300
301            byte[] byteBuff = sb.toString().getBytes();
302
303            outStream = context.openFileOutput(fileName, Context.MODE_PRIVATE);
304            if (outStream != null) {
305                outStream.write(byteBuff, 0, byteBuff.length);
306                fileUri = Uri.fromFile(new File(context.getFilesDir(), fileName));
307                if (fileUri != null) {
308                    if (D) Log.d(TAG, "Created one file for shared content: "
309                            + fileUri.toString());
310                }
311            }
312        } catch (FileNotFoundException e) {
313            Log.e(TAG, "FileNotFoundException: " + e.toString());
314            e.printStackTrace();
315        } catch (IOException e) {
316            Log.e(TAG, "IOException: " + e.toString());
317        } catch (Exception e) {
318            Log.e(TAG, "Exception: " + e.toString());
319        } finally {
320            try {
321                if (outStream != null) {
322                    outStream.close();
323                }
324            } catch (IOException e) {
325                e.printStackTrace();
326            }
327        }
328        return fileUri;
329    }
330
331    /**
332     * Escape some special character as HTML escape sequence.
333     *
334     * @param text Text to be displayed using WebView.
335     * @return Text correctly escaped.
336     */
337    private static String escapeCharacterToDisplay(String text) {
338        Pattern pattern = PLAIN_TEXT_TO_ESCAPE;
339        Matcher match = pattern.matcher(text);
340
341        if (match.find()) {
342            StringBuilder out = new StringBuilder();
343            int end = 0;
344            do {
345                int start = match.start();
346                out.append(text.substring(end, start));
347                end = match.end();
348                int c = text.codePointAt(start);
349                if (c == ' ') {
350                    // Escape successive spaces into series of "&nbsp;".
351                    for (int i = 1, n = end - start; i < n; ++i) {
352                        out.append("&nbsp;");
353                    }
354                    out.append(' ');
355                } else if (c == '\r' || c == '\n') {
356                    out.append("<br>");
357                } else if (c == '<') {
358                    out.append("&lt;");
359                } else if (c == '>') {
360                    out.append("&gt;");
361                } else if (c == '&') {
362                    out.append("&amp;");
363                }
364            } while (match.find());
365            out.append(text.substring(end));
366            text = out.toString();
367        }
368        return text;
369    }
370}
371