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