NFCAdpater.enableReaderMode(...) doesn't work consistently if booting in Kiosk mode activity
Asked Answered
A

3

4

I have an application which starts in Kiosk mode and should read and react on NFCTags. It's using enableReaderMode on the NFCAdapter in onResume to start reading them. Everything works fine if the app is e.g. (re-)started during development. However, if I reboot the device (and the activity gets started automatically) the activity is only sometimes put into the right mode, but often only plays the NFC system sound and my handleTag is not called.

From what I logged, the NFCAdapter setup code I have is correctly called in all circumstances

I tried enableForegroundDispatch as well, but there's the same effect. I also tried periodically recalling enableReaderMode but it also has the same effect.

Anybody has an idea what's going on?

Update

I see this error message in the logs when I try to set the reader mode in the cases where it fails

NfcService: setReaderMode: Caller is not in foreground and is not system process.

Although the activity is clearly visible in the forgreound.

Phone is a Google Pixel 3

The application is device owner through

adb shell dpm set-device-owner ...

The manifest of the application

<application
    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"
    android:testOnly="true">

    <!-- snip DeviceAdminReceiver -->

    <activity
        android:name=".FullscreenActivity"
        android:screenOrientation="reverseLandscape"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name"
        android:theme="@style/FullscreenTheme">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.HOME" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" />

The FullscreenActivity which should handle the NFC Tag

public class FullscreenActivity extends AppCompatActivity {
  NfcAdapter mAdapter;
  private DevicePolicyManager mDevicePolicyManager;
  private ComponentName mAdminComponentName;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mDevicePolicyManager = (DevicePolicyManager) getSystemService(
            Context.DEVICE_POLICY_SERVICE);
    if (mDevicePolicyManager.isDeviceOwnerApp(getPackageName())) {
        mAdminComponentName = MyDeviceAdminReceiver.getComponentName(this);

        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MAIN);
        intentFilter.addCategory(Intent.CATEGORY_HOME);
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        mDevicePolicyManager.addPersistentPreferredActivity(
                mAdminComponentName, intentFilter,
                new ComponentName(getPackageName(),
                        FullscreenActivity.class.getName()));

        mDevicePolicyManager.setLockTaskPackages(mAdminComponentName,
                new String[]{getPackageName()});

        mDevicePolicyManager.setKeyguardDisabled(mAdminComponentName, true);

    }
    startLockTask();
  }

  @Override
  public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        setFullscreenFlags();
    }
  }

  private void setFullscreenFlags() {
    getWindow().getDecorView()
            .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);

    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
  }

  @Override
  protected void onResume() {
    super.onResume();
    setFullscreenFlags();
    mAdapter = NfcAdapter.getDefaultAdapter(this);
    setupNfcAdapter();
  }

  private void setupNfcAdapter() {
    if (mAdapter == null) return;

    Bundle options = new Bundle();
    // No sure this is needed
    options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 50000);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            new Intent(this, getClass())
                    .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    mAdapter.enableReaderMode(this, this::handleTag,
            NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS |
                    NfcAdapter.FLAG_READER_NFC_A |
                    NfcAdapter.FLAG_READER_NFC_B |
                    NfcAdapter.FLAG_READER_NFC_F |
                    NfcAdapter.FLAG_READER_NFC_V, options);
  }

  @Override
  protected void onPause() {
    super.onPause();

    if (mAdapter != null) {
        mAdapter.disableReaderMode(this);
    }
  }

  private void handleTag(Tag tag) {
    Log.d("NFCADAPTER", "tag detected");
  }

}

Askja answered 10/4, 2020 at 14:30 Comment(2)
I wonder why periodically recalling enableReaderMode does not help you as it helped me (on Galaxy A40 if that matters) with the same issue. I call enableReaderMode in 100 ms interval for the first second after I need to enable the reader mode. Thank you for posting update as it really helped me to pin the problem (Caller is not in foreground and is not system process). See this question as well.Lutero
I've filed issuetracker.google.com/issues/172961444 "NfcAdapater.enableReaderMode() fails if run from Home app on boot" I haven't had success with the above workarounds.Bedmate
A
1

I found a solution (well, more a workaround) that works for my situation.

I think what happens is that the NfcService is not aware that the activity is running in the foreground. The NfcService keeps track of the foreground activity through a ForegroundUtils which leverages an IProcessObserver.

What I think is happening is that my activity sometimes becomes the foreground activity before this process observer is setup and therefore the NfcService thinks my activity is not runnning in the foreground, preventing the call on the read method.

What I did as a workaround is to receive NfcAdapter.STATE_ON changes by registering a receiver on NfcAdapter.ACTION_ADAPTER_STATE_CHANGED in the activity. If this event is received this is considered a situation as described above and I kill and restart the app (see [1]). This is now observed by the ForgroundUtils and I'm able to get into reader mode.

[1] How do I programmatically "restart" an Android app?

Askja answered 13/4, 2020 at 19:0 Comment(1)
Does listening for ACTION_ADAPTER_STATE_CHANGED solve this issue when activity starts with NFC already enabled? As far as I know you won't get any notification in this case (as NFC adapter simply does not change its state in this situation)...as I wrote in comment under the question -- repeated call of enableReaderMode with 100ms delay worked for me (which is much simpler than restarting the whole activity)Lutero
C
0

Update: may be another system App is starting after yours and taking the foreground.

I think you can force your app to Foreground just before you enable reader mode? e.g. https://mcmap.net/q/672649/-bring-application-back-to-front

No idea what is going on, other than thinking it is a timing issue.

BUT two things that might help.

  1. Try checking NfcAdapter.isEnabled() is true to help determine if the NFC hardware is actually available before your try enableReaderMode

  2. Setup a broadcaster receiver to log the NFC service states and enableReaderMode when it is ready as well as in onResume.

This should be more reliable than polling to see if the adapter is available at a later time.

This can be done with the following code:-

protected void onCreate(Bundle savedInstanceState) {
  // All the normal onCreate Stuff

// Listen for changes NFC settings
        IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
        this.registerReceiver(mReceiver, filter);
}


private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();

            if (action != null && action.equals(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)) {
                final int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
                        NfcAdapter.STATE_OFF);
                switch (state) {
                    case NfcAdapter.STATE_OFF:
                        Log.d("NFCADAPTER", "Adapter Off");
                        break;
                    case NfcAdapter.STATE_TURNING_OFF:
                        Log.d("NFCADAPTER", "Adapter Turning Off");
                        break;
                    case NfcAdapter.STATE_TURNING_ON:
                        Log.d("NFCADAPTER", "Adapter Turning On"); 
                        break;
                    case NfcAdapter.STATE_ON:
                        Log.d("NFCADAPTER", "Adapter On"); 
                        setupNfcAdapter();
                        break;
                }
            }
        }
    };

Capricorn answered 10/4, 2020 at 15:4 Comment(1)
That didn't help. Adapter is deemed ON but it still doesn't work. Even if I wait 1min before getting the adapter it doesn't work. However, I found some log message and it looks like the activity is not deemed to run in the foreground. Will update the quesiton.Askja
P
0

I am not sure if anyone has found a solution. But I think I have found a way around it.

I have observed that when we go to another activity and get back to the activity where the NFC is hosted, the NFC service seems to be restored.

So I have created an empty activity, that has no layout, that runs as a separate process. And when this error occurs, I just launch this activity and after 5 seconds this activity kills itself and returns back to the host activity.

Please let me know if anyone needs more details. I have just shared the overview. Hope it helps.

Pug answered 20/6, 2022 at 3:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.