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