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 AlertDialog.THEME_DEVICE_DEFAULT_LIGHT); 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