• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle savedInstanceState)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      */
launchDevicePicker()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. */
isBluetoothAllowed()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 
creatFileForSharedContent(Context context, CharSequence shareContent)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      */
escapeCharacterToDisplay(String text)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 
sendFileInfo( String mimeType, String uriString, boolean isHandover, boolean fromExternal)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 
showToast(final String msg)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