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.ActivityManager;
23 import android.app.ActivityManagerNative;
24 import android.app.AlertDialog;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.ClipData;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.PackageManager;
32 import android.net.Uri;
33 import android.nfc.BeamShareData;
34 import android.nfc.NdefMessage;
35 import android.nfc.NdefRecord;
36 import android.nfc.NfcAdapter;
37 import android.os.Bundle;
38 import android.os.UserHandle;
39 import android.os.RemoteException;
40 import android.util.Log;
41 import android.util.EventLog;
42 import android.webkit.URLUtil;
43 import android.Manifest.permission;
44 import android.widget.Toast;
45 
46 import com.android.internal.R;
47 
48 /**
49  * This class is registered by NfcService to handle
50  * ACTION_SHARE intents. It tries to parse data contained
51  * in ACTION_SHARE intents in either a content/file Uri,
52  * which can be sent using NFC handover, or alternatively
53  * it tries to parse texts and URLs to store them in a simple
54  * Text or Uri NdefRecord. The data is then passed on into
55  * NfcService to transmit on NFC tap.
56  *
57  */
58 public class BeamShareActivity extends Activity {
59     static final String TAG ="BeamShareActivity";
60     static final boolean DBG = false;
61 
62     ArrayList<Uri> mUris;
63     NdefMessage mNdefMessage;
64     NfcAdapter mNfcAdapter;
65     Intent mLaunchIntent;
66 
67     @Override
onCreate(Bundle savedInstanceState)68     protected void onCreate(Bundle savedInstanceState) {
69         super.onCreate(savedInstanceState);
70         mUris = new ArrayList<Uri>();
71         mNdefMessage = null;
72         mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
73         mLaunchIntent = getIntent();
74         if (mNfcAdapter == null) {
75             Log.e(TAG, "NFC adapter not present.");
76             finish();
77         } else {
78             if (!mNfcAdapter.isEnabled()) {
79                 showNfcDialogAndExit(com.android.nfc.R.string.beam_requires_nfc_enabled);
80             } else {
81                 parseShareIntentAndFinish(mLaunchIntent);
82             }
83         }
84     }
85 
86     @Override
onDestroy()87     protected void onDestroy() {
88         try {
89             unregisterReceiver(mReceiver);
90         } catch (Exception e) {
91             Log.w(TAG, e.getMessage());
92         }
93         super.onDestroy();
94     }
95 
showNfcDialogAndExit(int msgId)96     private void showNfcDialogAndExit(int msgId) {
97         IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
98         registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
99 
100         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
101                 com.android.nfc.R.style.DialogAlertDayNight);
102         dialogBuilder.setMessage(msgId);
103         dialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() {
104             @Override
105             public void onCancel(DialogInterface dialogInterface) {
106                 finish();
107             }
108         });
109         dialogBuilder.setPositiveButton(R.string.yes,
110                 new DialogInterface.OnClickListener() {
111                     @Override
112                     public void onClick(DialogInterface dialog, int id) {
113                         if (!mNfcAdapter.isEnabled()) {
114                             mNfcAdapter.enable();
115                             // Wait for enable broadcast
116                         } else {
117                             parseShareIntentAndFinish(mLaunchIntent);
118                         }
119                     }
120                 });
121         dialogBuilder.setNegativeButton(R.string.no,
122                 new DialogInterface.OnClickListener() {
123                     @Override
124                     public void onClick(DialogInterface dialogInterface, int i) {
125                         finish();
126                     }
127                 });
128         dialogBuilder.show();
129     }
130 
tryUri(Uri uri)131     void tryUri(Uri uri) {
132         if (uri.getScheme().equalsIgnoreCase("content") ||
133                 uri.getScheme().equalsIgnoreCase("file")) {
134             // Typically larger data, this can be shared using NFC handover
135             mUris.add(uri);
136         } else {
137             // Just put this Uri in an NDEF message
138             mNdefMessage = new NdefMessage(NdefRecord.createUri(uri));
139         }
140     }
141 
tryText(String text)142     void tryText(String text) {
143         if (URLUtil.isValidUrl(text)) {
144             Uri parsedUri = Uri.parse(text);
145             tryUri(parsedUri);
146         } else {
147             mNdefMessage = new NdefMessage(NdefRecord.createTextRecord(null, text));
148         }
149     }
150 
parseShareIntentAndFinish(Intent intent)151     public void parseShareIntentAndFinish(Intent intent) {
152         if (intent == null || intent.getAction() == null ||
153                 (!intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND) &&
154                 !intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND_MULTIPLE))) return;
155 
156         // First, see if the intent contains clip-data, and if so get data from there
157         ClipData clipData = intent.getClipData();
158         if (clipData != null && clipData.getItemCount() > 0) {
159             for (int i = 0; i < clipData.getItemCount(); i++) {
160                 ClipData.Item item = clipData.getItemAt(i);
161                 // First try to get an Uri
162                 Uri uri = item.getUri();
163                 String plainText = null;
164                 try {
165                     plainText = item.coerceToText(this).toString();
166                 } catch (IllegalStateException e) {
167                     if (DBG) Log.d(TAG, e.getMessage());
168                     continue;
169                 }
170                 if (uri != null) {
171                     if (DBG) Log.d(TAG, "Found uri in ClipData.");
172                     tryUri(uri);
173                 } else if (plainText != null) {
174                     if (DBG) Log.d(TAG, "Found text in ClipData.");
175                     tryText(plainText);
176                 } else {
177                     if (DBG) Log.d(TAG, "Did not find any shareable data in ClipData.");
178                 }
179             }
180         } else {
181             if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND)) {
182                 final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
183                 final CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
184                 if (uri != null) {
185                     if (DBG) Log.d(TAG, "Found uri in ACTION_SEND intent.");
186                     tryUri(uri);
187                 } else if (text != null) {
188                     if (DBG) Log.d(TAG, "Found EXTRA_TEXT in ACTION_SEND intent.");
189                     tryText(text.toString());
190                 } else {
191                     if (DBG) Log.d(TAG, "Did not find any shareable data in ACTION_SEND intent.");
192                 }
193             } else {
194                 final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
195                 final ArrayList<CharSequence> texts = intent.getCharSequenceArrayListExtra(
196                         Intent.EXTRA_TEXT);
197 
198                 if (uris != null && uris.size() > 0) {
199                     for (Uri uri : uris) {
200                         if (DBG) Log.d(TAG, "Found uri in ACTION_SEND_MULTIPLE intent.");
201                         tryUri(uri);
202                     }
203                 } else if (texts != null && texts.size() > 0) {
204                     // Try EXTRA_TEXT, but just for the first record
205                     if (DBG) Log.d(TAG, "Found text in ACTION_SEND_MULTIPLE intent.");
206                     tryText(texts.get(0).toString());
207                 } else {
208                     if (DBG) Log.d(TAG, "Did not find any shareable data in " +
209                             "ACTION_SEND_MULTIPLE intent.");
210                 }
211             }
212         }
213 
214         BeamShareData shareData = null;
215         UserHandle myUserHandle = new UserHandle(UserHandle.myUserId());
216         if (mUris.size() > 0) {
217             // Uris have our first preference for sharing
218             Uri[] uriArray = new Uri[mUris.size()];
219             int numValidUris = 0;
220             for (Uri uri : mUris) {
221                 try {
222                     int uid = ActivityManagerNative.getDefault().getLaunchedFromUid(getActivityToken());
223                     if (uri.getScheme().equalsIgnoreCase("file") &&
224                             getApplicationContext().checkPermission(permission.READ_EXTERNAL_STORAGE, -1, uid) !=
225                             PackageManager.PERMISSION_GRANTED) {
226                         Toast.makeText(getApplicationContext(),
227                                         com.android.nfc.R.string.beam_requires_external_storage_permission,
228                                         Toast.LENGTH_SHORT).show();
229                         Log.e(TAG, "File based Uri doesn't have External Storage Permission.");
230                         EventLog.writeEvent(0x534e4554, "37287958", uid, uri.getPath());
231                         break;
232                     }
233                     grantUriPermission("com.android.nfc", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
234                     uriArray[numValidUris++] = uri;
235                     if (DBG) Log.d(TAG, "Found uri: " + uri);
236                 } catch (SecurityException e) {
237                     Log.e(TAG, "Security exception granting uri permission to NFC process.");
238                     break;
239                 } catch (RemoteException e) {
240                     Log.e(TAG, "Remote exception accessing uid of the calling process.");
241                     break;
242                 }
243             }
244             if (numValidUris != 0 && numValidUris == mUris.size()) {
245                 shareData = new BeamShareData(null, uriArray, myUserHandle, 0);
246             } else {
247                 // No uris left
248                 shareData = new BeamShareData(null, null, myUserHandle, 0);
249             }
250         } else if (mNdefMessage != null) {
251             shareData = new BeamShareData(mNdefMessage, null, myUserHandle, 0);
252             if (DBG) Log.d(TAG, "Created NDEF message:" + mNdefMessage.toString());
253         } else {
254             if (DBG) Log.d(TAG, "Could not find any data to parse.");
255             // Activity may have set something to share over NFC, so pass on anyway
256             shareData = new BeamShareData(null, null, myUserHandle, 0);
257         }
258         mNfcAdapter.invokeBeam(shareData);
259         finish();
260     }
261 
262     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
263         @Override
264         public void onReceive(Context context, Intent intent) {
265             String action = intent.getAction();
266             if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(intent.getAction())) {
267                 int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
268                         NfcAdapter.STATE_OFF);
269                 if (state == NfcAdapter.STATE_ON) {
270                     parseShareIntentAndFinish(mLaunchIntent);
271                 }
272             }
273         }
274     };
275 }
276