1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.nfc;
18 
19 import java.util.ArrayList;
20 
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.ClipData;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.net.Uri;
30 import android.nfc.BeamShareData;
31 import android.nfc.NdefMessage;
32 import android.nfc.NdefRecord;
33 import android.nfc.NfcAdapter;
34 import android.os.Bundle;
35 import android.os.UserHandle;
36 import android.util.Log;
37 import android.webkit.URLUtil;
38 
39 import com.android.internal.R;
40 
41 /**
42  * This class is registered by NfcService to handle
43  * ACTION_SHARE intents. It tries to parse data contained
44  * in ACTION_SHARE intents in either a content/file Uri,
45  * which can be sent using NFC handover, or alternatively
46  * it tries to parse texts and URLs to store them in a simple
47  * Text or Uri NdefRecord. The data is then passed on into
48  * NfcService to transmit on NFC tap.
49  *
50  */
51 public class BeamShareActivity extends Activity {
52     static final String TAG ="BeamShareActivity";
53     static final boolean DBG = false;
54 
55     ArrayList<Uri> mUris;
56     NdefMessage mNdefMessage;
57     NfcAdapter mNfcAdapter;
58     Intent mLaunchIntent;
59 
60     @Override
onCreate(Bundle savedInstanceState)61     protected void onCreate(Bundle savedInstanceState) {
62         super.onCreate(savedInstanceState);
63         mUris = new ArrayList<Uri>();
64         mNdefMessage = null;
65         mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
66         mLaunchIntent = getIntent();
67         if (mNfcAdapter == null) {
68             Log.e(TAG, "NFC adapter not present.");
69             finish();
70         } else {
71             if (!mNfcAdapter.isEnabled()) {
72                 showNfcDialogAndExit(com.android.nfc.R.string.beam_requires_nfc_enabled);
73             } else {
74                 parseShareIntentAndFinish(mLaunchIntent);
75             }
76         }
77     }
78 
79 
showNfcDialogAndExit(int msgId)80     private void showNfcDialogAndExit(int msgId) {
81         IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
82         registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
83 
84         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
85                 AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
86         dialogBuilder.setMessage(msgId);
87         dialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() {
88             @Override
89             public void onCancel(DialogInterface dialogInterface) {
90                 finish();
91             }
92         });
93         dialogBuilder.setPositiveButton(R.string.yes,
94                 new DialogInterface.OnClickListener() {
95                     @Override
96                     public void onClick(DialogInterface dialog, int id) {
97                         if (!mNfcAdapter.isEnabled()) {
98                             mNfcAdapter.enable();
99                             // Wait for enable broadcast
100                         } else {
101                             parseShareIntentAndFinish(mLaunchIntent);
102                         }
103                     }
104                 });
105         dialogBuilder.setNegativeButton(R.string.no,
106                 new DialogInterface.OnClickListener() {
107                     @Override
108                     public void onClick(DialogInterface dialogInterface, int i) {
109                         finish();
110                     }
111                 });
112         dialogBuilder.show();
113     }
114 
tryUri(Uri uri)115     void tryUri(Uri uri) {
116         if (uri.getScheme().equalsIgnoreCase("content") ||
117                 uri.getScheme().equalsIgnoreCase("file")) {
118             // Typically larger data, this can be shared using NFC handover
119             mUris.add(uri);
120         } else {
121             // Just put this Uri in an NDEF message
122             mNdefMessage = new NdefMessage(NdefRecord.createUri(uri));
123         }
124     }
125 
tryText(String text)126     void tryText(String text) {
127         if (URLUtil.isValidUrl(text)) {
128             Uri parsedUri = Uri.parse(text);
129             tryUri(parsedUri);
130         } else {
131             mNdefMessage = new NdefMessage(NdefRecord.createTextRecord(null, text));
132         }
133     }
134 
parseShareIntentAndFinish(Intent intent)135     public void parseShareIntentAndFinish(Intent intent) {
136         if (intent == null || (!intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND) &&
137                 !intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND_MULTIPLE))) return;
138 
139         // First, see if the intent contains clip-data, and if so get data from there
140         ClipData clipData = intent.getClipData();
141         if (clipData != null && clipData.getItemCount() > 0) {
142             for (int i = 0; i < clipData.getItemCount(); i++) {
143                 ClipData.Item item = clipData.getItemAt(i);
144                 // First try to get an Uri
145                 Uri uri = item.getUri();
146                 String plainText = null;
147                 try {
148                     plainText = item.coerceToText(this).toString();
149                 } catch (IllegalStateException e) {
150                     if (DBG) Log.d(TAG, e.getMessage());
151                     continue;
152                 }
153                 if (uri != null) {
154                     if (DBG) Log.d(TAG, "Found uri in ClipData.");
155                     tryUri(uri);
156                 } else if (plainText != null) {
157                     if (DBG) Log.d(TAG, "Found text in ClipData.");
158                     tryText(plainText);
159                 } else {
160                     if (DBG) Log.d(TAG, "Did not find any shareable data in ClipData.");
161                 }
162             }
163         } else {
164             if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND)) {
165                 final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
166                 final CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
167                 if (uri != null) {
168                     if (DBG) Log.d(TAG, "Found uri in ACTION_SEND intent.");
169                     tryUri(uri);
170                 } else if (text != null) {
171                     if (DBG) Log.d(TAG, "Found EXTRA_TEXT in ACTION_SEND intent.");
172                     tryText(text.toString());
173                 } else {
174                     if (DBG) Log.d(TAG, "Did not find any shareable data in ACTION_SEND intent.");
175                 }
176             } else {
177                 final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
178                 final ArrayList<CharSequence> texts = intent.getCharSequenceArrayListExtra(
179                         Intent.EXTRA_TEXT);
180 
181                 if (uris != null && uris.size() > 0) {
182                     for (Uri uri : uris) {
183                         if (DBG) Log.d(TAG, "Found uri in ACTION_SEND_MULTIPLE intent.");
184                         tryUri(uri);
185                     }
186                 } else if (texts != null && texts.size() > 0) {
187                     // Try EXTRA_TEXT, but just for the first record
188                     if (DBG) Log.d(TAG, "Found text in ACTION_SEND_MULTIPLE intent.");
189                     tryText(texts.get(0).toString());
190                 } else {
191                     if (DBG) Log.d(TAG, "Did not find any shareable data in " +
192                             "ACTION_SEND_MULTIPLE intent.");
193                 }
194             }
195         }
196 
197         BeamShareData shareData = null;
198         UserHandle myUserHandle = new UserHandle(UserHandle.myUserId());
199         if (mUris.size() > 0) {
200             // Uris have our first preference for sharing
201             Uri[] uriArray = new Uri[mUris.size()];
202             int numValidUris = 0;
203             for (Uri uri : mUris) {
204                 try {
205                     grantUriPermission("com.android.nfc", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
206                     uriArray[numValidUris++] = uri;
207                     if (DBG) Log.d(TAG, "Found uri: " + uri);
208                 } catch (SecurityException e) {
209                     Log.e(TAG, "Security exception granting uri permission to NFC process.");
210                     numValidUris = 0;
211                     break;
212                 }
213             }
214             if (numValidUris > 0) {
215                 shareData = new BeamShareData(null, uriArray, myUserHandle, 0);
216             } else {
217                 // No uris left
218                 shareData = new BeamShareData(null, null, myUserHandle, 0);
219             }
220         } else if (mNdefMessage != null) {
221             shareData = new BeamShareData(mNdefMessage, null, myUserHandle, 0);
222             if (DBG) Log.d(TAG, "Created NDEF message:" + mNdefMessage.toString());
223         } else {
224             if (DBG) Log.d(TAG, "Could not find any data to parse.");
225             // Activity may have set something to share over NFC, so pass on anyway
226             shareData = new BeamShareData(null, null, myUserHandle, 0);
227         }
228         mNfcAdapter.invokeBeam(shareData);
229         finish();
230     }
231 
232     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
233         @Override
234         public void onReceive(Context context, Intent intent) {
235             String action = intent.getAction();
236             if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(intent.getAction())) {
237                 int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
238                         NfcAdapter.STATE_OFF);
239                 if (state == NfcAdapter.STATE_ON) {
240                     parseShareIntentAndFinish(mLaunchIntent);
241                 }
242             }
243         }
244     };
245 }
246