I am learning Android and NFC programming through the official android developer tutorial. What I want is to write an app that will be triggered by NFC tag. When the app starts, I want it to display a toast message containing the UID of the scanned tag. My simple code to achieve this is:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
Toast.makeText(getApplicationContext(), "UID: " + bin2hex(tag.getId()), Toast.LENGTH_SHORT).show();
}
// Parsing binary to string
static String bin2hex(byte[] data) {
return String.format("%0" + (data.length * 2) + "X", new BigInteger(1,data));
}
I also update the manifest file to enable NFC as shown below:
<uses-permission android:name="android.permission.NFC"/>
....
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
<activity
android:name="com.testapp.testnfc.MainActivity"
android:label="#string/app_name" >
.....
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
Then I use "NFC Task Launcher" app from Play Store to write into my tag so that the tag will trigger my new app. After creating the tag, tapping on it will successfully start my new app, but the app fails to display the toast of the UID and it will crash stating "Unfortunately Test NFC has stopped." What did I miss to cause this crash?
Try it like:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
Tag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
Toast.makeText(this,bin2hex(tag.getId()),Toast.LENGTH_LONG).show();
}
}
Related
Preconditions
1. App starts with LinkActivity, at this point we have no deep link intent, it's ok.
Main activity launched. There we are able to click the deep link.
By clicking on deep link opens LinkActivity, uri is correct, referringParams json is not empty (ok). But...
When we replaying step 2: uri is correct, but the reffering params are empty: "{}"; All other tries are with the same result.
Only when we pausing the app (for example switching to the recent apps menu) and then returning to the app - deep link works as expected, but only at first try. May be some issues with the session close (but in the current version of the sdk it self controls session close)
public class LinkActivity extends AppCompatActivity {
private static final String TAG = LinkActivity.class.getSimpleName();
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
#Override
protected void onStart() {
super.onStart();
Uri uri = getIntent().getData();
Log.w(TAG, "uri: " + uri);
Branch.getInstance().initSession(new Branch.BranchReferralInitListener() {
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
Log.w(TAG, "json: " + referringParams);
startActivity(new Intent(LinkActivity.this, MainActivity.class));
}
}, uri, this);
}
}
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
public class BranchApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Branch.enableLogging();
Branch.getAutoInstance(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.myapp">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".BranchApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".LinkActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="myapp.link"
android:scheme="https" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"/>
<meta-data
android:name="io.branch.sdk.BranchKey"
android:value="#string/branch_io_live_key" />
<meta-data
android:name="io.branch.sdk.BranchKey.test"
android:value="#string/branch_io_test_key" />
<meta-data
android:name="io.branch.sdk.TestMode"
android:value="false" />
</application>
</manifest>
implementation "io.branch.sdk.android:library:2.14.3"
Update:
Even with android:launchMode="singleInstance" for LinkActivity steel reproduces (I don't think this is the case).
Udpate2:
Bhardwaj mentioned that no need to call initSession when we initing Branch via getAutoInstance. But how to get refferingParams from uri in that case?
Update3:
From the Branch.checkIntentForSessionRestart doc:
Check for forced session restart. The Branch session is restarted if
the incoming intent has branch_force_new_session set to true. This is
for supporting opening a deep link path while app is already running
in the foreground. Such as clicking push notification while app in
foreground.
So, My desired behavior is matches this description. But how to force session restart?
You can try as mentioned below :-
Branch.getAutoInstance(this) -> Branch.getAutoInstance(this, true)
Branch.getInstance(context) -> Branch.getInstance()
Do not call initSession when you have getAutoInstance()
if(!initiatedBranchDeepLinks) {
// Configure Branch.io
initiatedBranchDeepLinks = true;
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener(){
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
if (error == null) {
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
// params will be empty if no data found
// ... insert custom logic here ...
String message = "Branch.io onInitFinished. Params: " + referringParams.toString();
Log.d(TAG, message);
} else {
Log.i(TAG, error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
Here is Branch Test Bed app:
https://github.com/BranchMetrics/android-branch-deep-linking/tree/master/Branch-SDK-TestBed
You can use this as a reference and see what you are doing incorrectly.
This could be caused by your Manifest configuration. In your <activity> tag, you should include android:launchMode="singleTask". See this section of our docs. This may explain why you are receiving the parameters the first time, but not receiving them on a re-open.
This question already has answers here:
How to use NFC ACTIONS
(5 answers)
Closed 3 years ago.
I'm creating application that do some action if hit NFC chip , I know the following information about NFC in Android please correct me if I am wrong
you can't register actions of the NFC to receiver in the manifest file , only activities .
each NFC chip has it's own unique id .
what I want to do is
while the Application is in the background or it's closed , if I hit NFC chip with id (ex 1234) my app lunched and do some action.
is that possible ?
if yes, how it can be achieved?
edit
here is my code , it does open when you check any NFC chip and get this action android.nfc.action.TECH_DISCOVERED
but when it open the action is android.intent.action.MAIN
MainActivity.java
public class MainActivity extends AppCompatActivity {
public static final String ACTION_CHECK_IN= "checked";
public static final String ACTION_CHECK_OUT= "unchecked";
private NfcAdapter mNfcAdapter;
boolean isCheckedIn = false;
private static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
isCheckedIn = Pref.getBoolean("check", false);
}
#Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume() called with: " + "");
String action = getIntent().getAction();
if (action != null) {
Log.d("MainActivity", "onCreate(" + action + ")");
if (action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED) || action.equals(NfcAdapter.ACTION_TAG_DISCOVERED) || action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
if (!isCheckedIn) {
isCheckedIn = true;
Pref.putBoolean("check", true);
Log.d("checking","IN");
} else {
isCheckedIn = false;
Pref.putBoolean("check", false);
Log.d("checking","Out");
}
}
}
if (!mNfcAdapter.isEnabled()) {
Toast.makeText(MainActivity.this, "Please open it ", Toast.LENGTH_SHORT).show();
}
/**
* It's important, that the activity is in the foreground.
* Otherwise an IllegalStateException is thrown.
*/
setupForegroundDispatch(this, mNfcAdapter);
}
#Override
protected void onPause() {
/**
* Call this before onPause, otherwise an IllegalArgumentException is thrown.
*/
stopForegroundDispatch(this, mNfcAdapter);
super.onPause();
}
#Override
protected void onDestroy() {
super.onDestroy();
}
public static void setupForegroundDispatch(Activity activity, NfcAdapter adapter) {
final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(
activity.getApplicationContext(), 0, intent, 0);
IntentFilter[] filters = new IntentFilter[2];
String[][] techList = new String[][]{};
// same filter as manifest - action, category
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_TAG_DISCOVERED);
filters[1] = new IntentFilter();
filters[1].addAction(NfcAdapter.ACTION_TECH_DISCOVERED);
adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
}
public static void stopForegroundDispatch(Activity activity, NfcAdapter adapter) {
adapter.disableForegroundDispatch(activity);
}
}
manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.srjo.pocnfcadapter">
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/filter_nfc" />
</activity>
</application>
</manifest>
According the the Android Developer site, it is possible for your app to filter NFC intents such as ACTION_NDEF_DISCOVERED.
First register your app in the manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.survivingwithandroid.nfc" >
....
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain"/>
</intent-filter>
<manifest>
Then you should use foreground dispatch (foreground app). Finally you use NFCAdapter to read the content (onNewIntent).
I wrote a detailed posts about NFC if you like give a look.
My application created files with a custom Mime type and stores them on Google Drive. The app can search and reopen these files just fine too. However, when I click the file in the Google Drive app (not my own app) the open flow does not work.
The Chooser Intent shows as expected with just my application listed, but when I select my application the Google Drive app briefly shows a downloading progress bar that never starts and then says there is an internal error.
My setup is below, I'm hoping somebody can tell me what causes this. although I would assume nothing is actually contacting my app by this point. The developer console has been filled in correctly as far as I know.
Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="...">
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:icon="#drawable/logo" android:label="#string/app_name"
android:allowBackup="true" android:theme="#style/AppTheme">
<meta-data android:name="com.google.android.gms.version" android:value="#integer/google_play_services_version" />
<activity android:name=".MainActivity" android:label="#string/app_name"
android:launchMode="singleTop" android:theme="#style/AppTheme">
<meta-data android:name="com.google.android.apps.drive.APP_ID" android:value="id=..." />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.apps.drive.DRIVE_OPEN" />
<data android:mimeType="#string/app_mime" />
<data android:mimeType="#string/file_mime" /> <!-- matches the file im clicking -->
</intent-filter>
</activity>
</application>
Activity
public class MainActivity extends Activity {
private static final String ACTION_DRIVE_OPEN = "com.google.android.apps.drive.DRIVE_OPEN";
private static final String EXTRA_DRIVE_OPEN_ID = "resourceId";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
#Override
protected void onResume() {
super.onResume();
handleIntent();
}
private void handleIntent() {
Intent intent = getIntent();
if (ACTION_DRIVE_OPEN.equals(intent.getAction())) {
if (intent.getType().equals(getString(R.string.file_mime))) {
String fileId = intent.getStringExtra(EXTRA_DRIVE_OPEN_ID);
if (fileId != null && !"".equals(fileId)) {
...
} else {
Log.e(TAG, "Drive_Open has no valid file id - " + fileId);
}
} else {
Log.e(TAG, "Drive_Open called on the wrong mime type - " + intent.getType() + " found, " + getString(R.string.file_mime) + " required");
}
}
}
After some testing this seems to happen after there is a timeout between the drive app connecting to the server to retrieve information and passing it to the other app (or any other fault).
The initial problem will give you a meaningful error but then the drive app seems to maintain a cache or something which cases the above error to be displayed on all future attempts.
The solution is to either clear the data of both apps (which can be problematic) or (I found easier) re-install the app you are developing/testing. Either method stops this problem reoccurring.
I am creating an app where two devices communicate over NFC and then one transmits to the server.
I am trying to develop this for both pre- and post-lollipop. The problem is that I create my NDEF message in one app and then receive it in another app. However when I try to receive in the other app, the NFC "New Tag collected" screen is opened instead of my app. This is a real problem for me, I'm just sending a string which I need to send to a web service.
Below is the manifest of the receiving application:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.devcompany.paymentcustomer" >
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".activities.HomeActivity"
android:launchMode="singleTop"
android:label="#string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Here is my code for sending:
mNfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
//If device is running lollipop remove the touch to beam action
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
mNfcAdapter.setNdefPushMessageCallback(nDefCallback, getActivity());
mNfcAdapter.invokeBeam(getActivity());
}
}, 2000);
}else{
//leave touch to beam action
mNfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
#Override
public NdefMessage createNdefMessage(NfcEvent event) {
NdefMessage message = new NdefMessage((new NdefRecord[]{createMime("application/com.devcompany.paymentvendor.fragments", mToBeam.getBytes()) }));
return message;
}
}, getActivity());
mNfcAdapter.setOnNdefPushCompleteCallback(
new NfcAdapter.OnNdefPushCompleteCallback() {
#Override
public void onNdefPushComplete(NfcEvent event) {
}
}, getActivity());
}
Here is my code for receiving:
public class HomeActivity extends ActionBarActivity {
private NfcAdapter mAdapter;
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_layout);
mAdapter = NfcAdapter.getDefaultAdapter(this);
textView = (TextView)findViewById(R.id.fortressLabel);
}
#Override
public void onResume(){
super.onResume();
if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())){
processIntent(getIntent());
}
}
#Override
public void onNewIntent(Intent intent) {
// onResume gets called after this to handle the intent
setIntent(intent);
}
private void processIntent(Intent intent){
//textView.setText(intent.getDataString());
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
NfcAdapter.EXTRA_NDEF_MESSAGES);
// only one message sent during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
Log.i(this.getLocalClassName(), intent.getDataString());
}
}
I have debugged my receiving code and stepped through it, when I hold the phones together, the code doesn't even enter the onResume method. The tags intent is launched first. I don't understand why this is happening. Can anyone help me on this one?
You are sending a MIME type record of type "application/com.devcompany.paymentvendor.fragments":
NdefMessage message = new NdefMessage(new NdefRecord[] {
NdefRecord.createMime("application/com.devcompany.paymentvendor.fragments",
mToBeam.getBytes())
});
Consequently, you also need to instruct your receiving activity to actually receive that MIME type record. You can register your app to open (and receive) upon detection of that MIME type in an NFC event by adding an NDEF_DISCOVERED intent filter for your activity:
<activity android:name=".activities.HomeActivity" ...>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/com.devcompany.paymentvendor.fragments" />
</intent-filter>
</activity>
I am facing issue in opening Email in MYApp when I made its launch mode to "singleInstance".
I have attached sample Android project which reads file name from email attachment and displays it on screen.
Works fine in case of onCreate but throws error in onNewIntent when apps launch mode is singleInstance.
Launchmode.java
package your.namespace.launchmode;
public class LaunchModeActivity extends Activity {
private static final int OPEN_ACT = 2;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String name = getAttachmetName(getIntent());
if(null != name)
{
TextView textv = (TextView) findViewById(R.id.attachmentnm);
textv.setText(name);
}
}
#Override
protected void onNewIntent(Intent savedInstanceState)
{
super.onNewIntent(savedInstanceState);
String name = getAttachmetName(savedInstanceState);
if(null != name)
{
TextView textv = (TextView) findViewById(R.id.attachmentnm);
textv.setText(name);
}
}
private String getAttachmetName(Intent intent) {
final Uri documentUri = intent.getData();
if(null != documentUri){
final String uriString = documentUri.toString();
String documentFilename = null;
final int mailIndexPos = uriString.lastIndexOf("/attachments");
if (mailIndexPos != -1) {
final Uri curi = documentUri;
final String [] projection = new String[] {OpenableColumns.DISPLAY_NAME};
final Cursor cursor = getApplicationContext().getContentResolver().query(curi, projection, null, null, null);
if (cursor != null) {
final int attIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (attIdx != -1) {
cursor.moveToFirst();
documentFilename = cursor.getString(attIdx);
}
cursor.close();
}
}
return documentFilename;
}
return null;
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, data);
if((resultCode == RESULT_OK) && (requestCode == OPEN_ACT))
{
Log.d("LaunchMode", "Second activity returned");
}
}
}
AndroidManifest
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.namespace.launchmode"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="com.google.android.gm.permission.READ_GMAIL"/>
<uses-permission android:name="com.google.android.gm.permission.WRITE_GMAIL"/>
<uses-permission android:name="com.google.android.providers.gmail.permission.READ_GMAIL"/>
<uses-permission android:name="com.google.android.providers.gmail.permission.WRITE_GMAIL"/>
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:label="#string/app_name"
android:launchMode="singleInstance"
android:name=".LaunchModeActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!-- docx -->
<data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
<!-- xlsx -->
<data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
<!-- pptx -->
<data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" />
<data android:mimeType="application/vnd.ms-excel" />
<data android:mimeType="application/msword" />
<data android:mimeType="application/vnd.ms-powerpoint" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
</manifest>
Steps to reproduce
1)Install apk on device.
2)Go to gmail native app on device, open any attachment(office document) to view.
3)Choose LaunchMode app to complete action.
4)LaunchMode app will display file name on screen.
This works fine for first time (onCreate flow) but when this app is switch in background and again I try 2,3,4 steps.. app crashes with error
E/DatabaseUtils(30615): java.lang.SecurityException: Permission Denial: reading com.google.android.gm.provider.MailProvider uri content://gmail-ls/qoconnect#gmail.com/messages/5/attachments/0.2/BEST/false from pid=32657, uid=10058 requires com.google.android.gm.permission.READ_GMAIL
E/DatabaseUtils(30615): at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:309)
E/DatabaseUtils(30615): at android.content.ContentProvider$Transport.bulkQuery(ContentProvider.java:178)
E/DatabaseUtils(30615): at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:111)
E/DatabaseUtils(30615): at android.os.Binder.execTransact(Binder.java:339)
E/DatabaseUtils(30615): at dalvik.system.NativeStart.run(Native Method)
D/AndroidRuntime(32657): Shutting down VM
I need to fix this as, I need to have single instance of Application and should get email attachment name too.
Please let me know If I am missing something here.
My question here is why it work in flow of onCreate and it wont work in case of onNewIntent
Note:
1)Works fine with 2.x phones
2) Works fine with Single top launch mode.
3) Some updates on Gmail app.link here:
You likely got a URI permission to read the file name when you received the intent and aren't using the permissions that you requested (READ_GMAIL and WRITE_GMAIL). The URI permission is valid only until your application finish()es, so you won't have it when you try to resume.
That's consistent with your experiences - it works when the intent is fresh, but not old ones. I think that WRITE_GMAIL is a signature permission and I am guessing that READ_GMAIL is as well. In that case, there is not much you can do. READ_ATTACHMENT might be a more appropriate permission for you to request.
More on URI permissions: http://developer.android.com/guide/topics/security/permissions.html#uri
Try removing the uses-permission tags from your manifest and see if you have the same experience. You can also try to examine the intent when you receive it by checking its flags.
checkCallingOrSelfUriPermission(documentUri , Intent.FLAG_GRANT_READ_URI_PERMISSION)
If you get 0 returned, you have been using URI permissions.
Like skoke said, you can no longer read from GMail if the intent that gave your permission is not fresh, i.e., it must be the original activity intent. If your intent is from onNewIntent then it probably won't succeed.
My solution isn't beautiful but seems to be working. In my onResume I called a custom function to see if I could access the Gmail content. If not, I showed the user a message and asked them to close the app and try again. Hey, at least it doesn't crash.