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 " ". 351 for (int i = 1, n = end - start; i < n; ++i) { 352 out.append(" "); 353 } 354 out.append(' '); 355 } else if (c == '\r' || c == '\n') { 356 out.append("<br>"); 357 } else if (c == '<') { 358 out.append("<"); 359 } else if (c == '>') { 360 out.append(">"); 361 } else if (c == '&') { 362 out.append("&"); 363 } 364 } while (match.find()); 365 out.append(text.substring(end)); 366 text = out.toString(); 367 } 368 return text; 369 } 370} 371