Merge changes from topic "nfcc_aon_listener"

* changes:
  Add NfcManagerTests
  Update ControllerAlwaysOnStateCallback to ControllerAlwaysOnListener
This commit is contained in:
George Chang
2021-04-15 15:09:00 +00:00
committed by Gerrit Code Review
11 changed files with 339 additions and 64 deletions

View File

@@ -6735,16 +6735,16 @@ package android.nfc {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnStateCallback(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
}
public static interface NfcAdapter.ControllerAlwaysOnStateCallback {
method public void onStateChanged(boolean);
public static interface NfcAdapter.ControllerAlwaysOnListener {
method public void onControllerAlwaysOnChanged(boolean);
}
public static interface NfcAdapter.NfcUnlockHandler {

View File

@@ -24,7 +24,7 @@ import android.nfc.Tag;
import android.nfc.TechListParcel;
import android.nfc.IAppCallback;
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcControllerAlwaysOnStateCallback;
import android.nfc.INfcControllerAlwaysOnListener;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
import android.nfc.INfcFCardEmulation;
@@ -76,6 +76,6 @@ interface INfcAdapter
boolean setControllerAlwaysOn(boolean value);
boolean isControllerAlwaysOn();
boolean isControllerAlwaysOnSupported();
void registerControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
void unregisterControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
}

View File

@@ -19,11 +19,11 @@ package android.nfc;
/**
* @hide
*/
oneway interface INfcControllerAlwaysOnStateCallback {
oneway interface INfcControllerAlwaysOnListener {
/**
* Called whenever the controller always on state changes
*
* @param isEnabled true if the state is enabled, false otherwise
*/
void onControllerAlwaysOnStateChanged(boolean isEnabled);
void onControllerAlwaysOnChanged(boolean isEnabled);
}

View File

@@ -67,7 +67,7 @@ import java.util.concurrent.Executor;
public final class NfcAdapter {
static final String TAG = "NFC";
private final NfcControllerAlwaysOnStateListener mControllerAlwaysOnStateListener;
private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
@@ -418,19 +418,19 @@ public final class NfcAdapter {
}
/**
* A callback to be invoked when NFC controller always on state changes.
* <p>Register your {@code ControllerAlwaysOnStateCallback} implementation with {@link
* NfcAdapter#registerControllerAlwaysOnStateCallback} and disable it with {@link
* NfcAdapter#unregisterControllerAlwaysOnStateCallback}.
* @see #registerControllerAlwaysOnStateCallback
* A listener to be invoked when NFC controller always on state changes.
* <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
* NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link
* NfcAdapter#unregisterControllerAlwaysOnListener}.
* @see #registerControllerAlwaysOnListener
* @hide
*/
@SystemApi
public interface ControllerAlwaysOnStateCallback {
public interface ControllerAlwaysOnListener {
/**
* Called on NFC controller always on state changes
*/
void onStateChanged(boolean isEnabled);
void onControllerAlwaysOnChanged(boolean isEnabled);
}
/**
@@ -748,7 +748,7 @@ public final class NfcAdapter {
mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
mTagRemovedListener = null;
mLock = new Object();
mControllerAlwaysOnStateListener = new NfcControllerAlwaysOnStateListener(getService());
mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
}
/**
@@ -2246,12 +2246,12 @@ public final class NfcAdapter {
* <p>This API is for the NFCC internal state management. It allows to discriminate
* the controller function from the NFC function by keeping the NFC controller on without
* any NFC RF enabled if necessary.
* <p>This call is asynchronous. Register a callback {@link #ControllerAlwaysOnStateCallback}
* by {@link #registerControllerAlwaysOnStateCallback} to find out when the operation is
* <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
* by {@link #registerControllerAlwaysOnListener} to find out when the operation is
* complete.
* <p>If this returns true, then either NFCC always on state has been set based on the value,
* or a {@link ControllerAlwaysOnStateCallback#onStateChanged(boolean)} will be invoked to
* indicate the state change.
* or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked
* to indicate the state change.
* If this returns false, then there is some problem that prevents an attempt to turn NFCC
* always on.
* @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
@@ -2344,37 +2344,37 @@ public final class NfcAdapter {
}
/**
* Register a {@link ControllerAlwaysOnStateCallback} to listen for NFC controller always on
* Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on
* state changes
* <p>The provided callback will be invoked by the given {@link Executor}.
* <p>The provided listener will be invoked by the given {@link Executor}.
*
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
* @param executor an {@link Executor} to execute given listener
* @param listener user implementation of the {@link ControllerAlwaysOnListener}
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
public void registerControllerAlwaysOnStateCallback(
public void registerControllerAlwaysOnListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull ControllerAlwaysOnStateCallback callback) {
mControllerAlwaysOnStateListener.register(executor, callback);
@NonNull ControllerAlwaysOnListener listener) {
mControllerAlwaysOnListener.register(executor, listener);
}
/**
* Unregister the specified {@link ControllerAlwaysOnStateCallback}
* <p>The same {@link ControllerAlwaysOnStateCallback} object used when calling
* {@link #registerControllerAlwaysOnStateCallback(Executor, ControllerAlwaysOnStateCallback)}
* Unregister the specified {@link ControllerAlwaysOnListener}
* <p>The same {@link ControllerAlwaysOnListener} object used when calling
* {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)}
* must be used.
*
* <p>Callbacks are automatically unregistered when application process goes away
* <p>Listeners are automatically unregistered when application process goes away
*
* @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
* @param listener user implementation of the {@link ControllerAlwaysOnListener}
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
public void unregisterControllerAlwaysOnStateCallback(
@NonNull ControllerAlwaysOnStateCallback callback) {
mControllerAlwaysOnStateListener.unregister(callback);
public void unregisterControllerAlwaysOnListener(
@NonNull ControllerAlwaysOnListener listener) {
mControllerAlwaysOnListener.unregister(listener);
}
}

View File

@@ -17,7 +17,7 @@
package android.nfc;
import android.annotation.NonNull;
import android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback;
import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
@@ -29,77 +29,77 @@ import java.util.concurrent.Executor;
/**
* @hide
*/
public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnStateCallback.Stub {
private static final String TAG = "NfcControllerAlwaysOnStateListener";
public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub {
private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName();
private final INfcAdapter mAdapter;
private final Map<ControllerAlwaysOnStateCallback, Executor> mCallbackMap = new HashMap<>();
private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>();
private boolean mCurrentState = false;
private boolean mIsRegistered = false;
public NfcControllerAlwaysOnStateListener(@NonNull INfcAdapter adapter) {
public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) {
mAdapter = adapter;
}
/**
* Register a {@link ControllerAlwaysOnStateCallback} with this
* {@link NfcControllerAlwaysOnStateListener}
* Register a {@link ControllerAlwaysOnListener} with this
* {@link NfcControllerAlwaysOnListener}
*
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
* @param executor an {@link Executor} to execute given listener
* @param listener user implementation of the {@link ControllerAlwaysOnListener}
*/
public void register(@NonNull Executor executor,
@NonNull ControllerAlwaysOnStateCallback callback) {
@NonNull ControllerAlwaysOnListener listener) {
synchronized (this) {
if (mCallbackMap.containsKey(callback)) {
if (mListenerMap.containsKey(listener)) {
return;
}
mCallbackMap.put(callback, executor);
mListenerMap.put(listener, executor);
if (!mIsRegistered) {
try {
mAdapter.registerControllerAlwaysOnStateCallback(this);
mAdapter.registerControllerAlwaysOnListener(this);
mIsRegistered = true;
} catch (RemoteException e) {
Log.w(TAG, "Failed to register ControllerAlwaysOnStateListener");
Log.w(TAG, "Failed to register");
}
}
}
}
/**
* Unregister the specified {@link ControllerAlwaysOnStateCallback}
* Unregister the specified {@link ControllerAlwaysOnListener}
*
* @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
* @param listener user implementation of the {@link ControllerAlwaysOnListener}
*/
public void unregister(@NonNull ControllerAlwaysOnStateCallback callback) {
public void unregister(@NonNull ControllerAlwaysOnListener listener) {
synchronized (this) {
if (!mCallbackMap.containsKey(callback)) {
if (!mListenerMap.containsKey(listener)) {
return;
}
mCallbackMap.remove(callback);
mListenerMap.remove(listener);
if (mCallbackMap.isEmpty() && mIsRegistered) {
if (mListenerMap.isEmpty() && mIsRegistered) {
try {
mAdapter.unregisterControllerAlwaysOnStateCallback(this);
mAdapter.unregisterControllerAlwaysOnListener(this);
} catch (RemoteException e) {
Log.w(TAG, "Failed to unregister ControllerAlwaysOnStateListener");
Log.w(TAG, "Failed to unregister");
}
mIsRegistered = false;
}
}
}
private void sendCurrentState(@NonNull ControllerAlwaysOnStateCallback callback) {
private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) {
synchronized (this) {
Executor executor = mCallbackMap.get(callback);
Executor executor = mListenerMap.get(listener);
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> callback.onStateChanged(
executor.execute(() -> listener.onControllerAlwaysOnChanged(
mCurrentState));
} finally {
Binder.restoreCallingIdentity(identity);
@@ -108,10 +108,10 @@ public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnSt
}
@Override
public void onControllerAlwaysOnStateChanged(boolean isEnabled) {
public void onControllerAlwaysOnChanged(boolean isEnabled) {
synchronized (this) {
mCurrentState = isEnabled;
for (ControllerAlwaysOnStateCallback cb : mCallbackMap.keySet()) {
for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) {
sendCurrentState(cb);
}
}

View File

@@ -0,0 +1,7 @@
{
"presubmit": [
{
"name": "NfcManagerTests"
}
]
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
android_test {
name: "NfcManagerTests",
static_libs: [
"androidx.test.ext.junit",
"androidx.test.rules",
"mockito-target-minus-junit4",
],
libs: [
"android.test.runner",
],
srcs: ["src/**/*.java"],
platform_apis: true,
certificate: "platform",
test_suites: ["device-tests"],
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.nfc">
<application>
<uses-library android:name="android.test.runner" />
</application>
<!-- This is a self-instrumenting test package. -->
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.nfc"
android:label="NFC Manager Tests">
</instrumentation>
</manifest>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration description="Config for NFC Manager test cases">
<option name="test-suite-tag" value="apct"/>
<option name="test-suite-tag" value="apct-instrumentation"/>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="NfcManagerTests.apk" />
</target_preparer>
<option name="test-suite-tag" value="apct"/>
<option name="test-tag" value="NfcManagerTests"/>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.nfc" />
<option name="hidden-api-checks" value="false"/>
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
</test>
</configuration>

View File

@@ -0,0 +1 @@
include /core/java/android/nfc/OWNERS

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nfc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
import android.os.RemoteException;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Test of {@link NfcControllerAlwaysOnListener}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NfcControllerAlwaysOnListenerTest {
private INfcAdapter mNfcAdapter = mock(INfcAdapter.class);
private Throwable mThrowRemoteException = new RemoteException("RemoteException");
private static Executor getExecutor() {
return new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
}
private static void verifyListenerInvoked(ControllerAlwaysOnListener listener) {
verify(listener, times(1)).onControllerAlwaysOnChanged(anyBoolean());
}
@Test
public void testRegister_RegisterUnregister() throws RemoteException {
NfcControllerAlwaysOnListener mListener =
new NfcControllerAlwaysOnListener(mNfcAdapter);
ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
// Verify that the state listener registered with the NFC Adapter
mListener.register(getExecutor(), mockListener1);
verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
// Register a second client and no new call to NFC Adapter
mListener.register(getExecutor(), mockListener2);
verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
// Unregister first listener
mListener.unregister(mockListener1);
verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any());
// Unregister second listener and the state listener registered with the NFC Adapter
mListener.unregister(mockListener2);
verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
verify(mNfcAdapter, times(1)).unregisterControllerAlwaysOnListener(any());
}
@Test
public void testRegister_FirstRegisterFails() throws RemoteException {
NfcControllerAlwaysOnListener mListener =
new NfcControllerAlwaysOnListener(mNfcAdapter);
ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
// Throw a remote exception whenever first registering
doThrow(mThrowRemoteException).when(mNfcAdapter).registerControllerAlwaysOnListener(
any());
mListener.register(getExecutor(), mockListener1);
verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
// No longer throw an exception, instead succeed
doNothing().when(mNfcAdapter).registerControllerAlwaysOnListener(any());
// Register a different listener
mListener.register(getExecutor(), mockListener2);
verify(mNfcAdapter, times(2)).registerControllerAlwaysOnListener(any());
// Ensure first and second listener were invoked
mListener.onControllerAlwaysOnChanged(true);
verifyListenerInvoked(mockListener1);
verifyListenerInvoked(mockListener2);
}
@Test
public void testRegister_RegisterSameListenerTwice() throws RemoteException {
NfcControllerAlwaysOnListener mListener =
new NfcControllerAlwaysOnListener(mNfcAdapter);
ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
// Register the same listener Twice
mListener.register(getExecutor(), mockListener);
mListener.register(getExecutor(), mockListener);
verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
// Invoke a state change and ensure the listener is only called once
mListener.onControllerAlwaysOnChanged(true);
verifyListenerInvoked(mockListener);
}
@Test
public void testNotify_AllListenersNotified() throws RemoteException {
NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
List<ControllerAlwaysOnListener> mockListeners = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
listener.register(getExecutor(), mockListener);
mockListeners.add(mockListener);
}
// Invoke a state change and ensure all listeners are invoked
listener.onControllerAlwaysOnChanged(true);
for (ControllerAlwaysOnListener mListener : mockListeners) {
verifyListenerInvoked(mListener);
}
}
@Test
public void testStateChange_CorrectValue() {
runStateChangeValue(true, true);
runStateChangeValue(false, false);
}
private void runStateChangeValue(boolean isEnabledIn, boolean isEnabledOut) {
NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
listener.register(getExecutor(), mockListener);
listener.onControllerAlwaysOnChanged(isEnabledIn);
verify(mockListener, times(1)).onControllerAlwaysOnChanged(isEnabledOut);
verify(mockListener, times(0)).onControllerAlwaysOnChanged(!isEnabledOut);
}
}