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