Merge changes I1f880aa6,Ie8ba8208,Ic45aa87c,I04c1263a am: e98351f355

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1504104

Change-Id: Ib7e4e7b928b6fd72d3277f35a4123d26d206fe4d
This commit is contained in:
Treehugger Robot
2020-12-04 17:42:33 +00:00
committed by Automerger Merge Worker
6 changed files with 553 additions and 15 deletions

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2020 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.uwb;
import android.annotation.NonNull;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
import android.uwb.UwbManager.AdapterStateCallback;
import android.uwb.UwbManager.AdapterStateCallback.StateChangedReason;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* @hide
*/
public class AdapterStateListener extends IUwbAdapterStateCallbacks.Stub {
private static final String TAG = "Uwb.StateListener";
private final IUwbAdapter mAdapter;
private boolean mIsRegistered = false;
private final Map<AdapterStateCallback, Executor> mCallbackMap = new HashMap<>();
@StateChangedReason
private int mAdapterStateChangeReason = AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN;
private boolean mAdapterEnabledState = false;
public AdapterStateListener(@NonNull IUwbAdapter adapter) {
mAdapter = adapter;
}
/**
* Register an {@link AdapterStateCallback} with this {@link AdapterStateListener}
*
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link AdapterStateCallback}
*/
public void register(@NonNull Executor executor, @NonNull AdapterStateCallback callback) {
synchronized (this) {
if (mCallbackMap.containsKey(callback)) {
return;
}
mCallbackMap.put(callback, executor);
if (!mIsRegistered) {
try {
mAdapter.registerAdapterStateCallbacks(this);
mIsRegistered = true;
} catch (RemoteException e) {
Log.w(TAG, "Failed to register adapter state callback");
executor.execute(() -> callback.onStateChanged(false,
AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN));
}
} else {
sendCurrentState(callback);
}
}
}
/**
* Unregister the specified {@link AdapterStateCallback}
*
* @param callback user implementation of the {@link AdapterStateCallback}
*/
public void unregister(@NonNull AdapterStateCallback callback) {
synchronized (this) {
if (!mCallbackMap.containsKey(callback)) {
return;
}
mCallbackMap.remove(callback);
if (mCallbackMap.isEmpty() && mIsRegistered) {
try {
mAdapter.unregisterAdapterStateCallbacks(this);
} catch (RemoteException e) {
Log.w(TAG, "Failed to unregister AdapterStateCallback with service");
}
mIsRegistered = false;
}
}
}
private void sendCurrentState(@NonNull AdapterStateCallback callback) {
synchronized (this) {
Executor executor = mCallbackMap.get(callback);
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> callback.onStateChanged(
mAdapterEnabledState, mAdapterStateChangeReason));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public void onAdapterStateChanged(boolean isEnabled, int reason) {
synchronized (this) {
@StateChangedReason int localReason =
convertToStateChangedReason(reason);
mAdapterEnabledState = isEnabled;
mAdapterStateChangeReason = localReason;
for (AdapterStateCallback cb : mCallbackMap.keySet()) {
sendCurrentState(cb);
}
}
}
private static @StateChangedReason int convertToStateChangedReason(
@StateChangeReason int reason) {
switch (reason) {
case StateChangeReason.ALL_SESSIONS_CLOSED:
return AdapterStateCallback.STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED;
case StateChangeReason.SESSION_STARTED:
return AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED;
case StateChangeReason.SYSTEM_POLICY:
return AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY;
case StateChangeReason.SYSTEM_BOOT:
return AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT;
case StateChangeReason.UNKNOWN:
default:
return AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN;
}
}
}

View File

@@ -98,11 +98,18 @@ interface IUwbAdapter {
int getMaxSimultaneousSessions();
/**
* Get the maximum number of remote devices per session
* Get the maximum number of remote devices per session when local device is initiator
*
* @return the maximum number of remote devices supported in a single session
*/
int getMaxRemoteDevicesPerSession();
int getMaxRemoteDevicesPerInitiatorSession();
/**
* Get the maximum number of remote devices per session when local device is responder
*
* @return the maximum number of remote devices supported in a single session
*/
int getMaxRemoteDevicesPerResponderSession();
/**
* Provides the capabilities and features of the device

View File

@@ -41,5 +41,10 @@ enum StateChangeReason {
* The adapter state changed because of a device system change.
*/
SYSTEM_POLICY,
/**
* Used to signal the first adapter state message after boot
*/
SYSTEM_BOOT,
}

View File

@@ -16,6 +16,7 @@
package android.uwb;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -23,10 +24,13 @@ import android.annotation.SystemService;
import android.content.Context;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -45,6 +49,8 @@ public final class UwbManager {
private IUwbAdapter mUwbAdapter;
private static final String SERVICE_NAME = "uwb";
private AdapterStateListener mAdapterStateListener;
/**
* Interface for receiving UWB adapter state changes
*/
@@ -109,6 +115,7 @@ public final class UwbManager {
*/
private UwbManager(IUwbAdapter adapter) {
mUwbAdapter = adapter;
mAdapterStateListener = new AdapterStateListener(adapter);
}
/**
@@ -140,8 +147,9 @@ public final class UwbManager {
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link AdapterStateCallback}
*/
public void registerAdapterStateCallback(Executor executor, AdapterStateCallback callback) {
throw new UnsupportedOperationException();
public void registerAdapterStateCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull AdapterStateCallback callback) {
mAdapterStateListener.register(executor, callback);
}
/**
@@ -153,8 +161,8 @@ public final class UwbManager {
*
* @param callback user implementation of the {@link AdapterStateCallback}
*/
public void unregisterAdapterStateCallback(AdapterStateCallback callback) {
throw new UnsupportedOperationException();
public void unregisterAdapterStateCallback(@NonNull AdapterStateCallback callback) {
mAdapterStateListener.unregister(callback);
}
/**
@@ -167,7 +175,11 @@ public final class UwbManager {
*/
@NonNull
public PersistableBundle getSpecificationInfo() {
throw new UnsupportedOperationException();
try {
return mUwbAdapter.getSpecificationInfo();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
@@ -176,7 +188,11 @@ public final class UwbManager {
* @return true if ranging is supported
*/
public boolean isRangingSupported() {
throw new UnsupportedOperationException();
try {
return mUwbAdapter.isRangingSupported();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Retention(RetentionPolicy.SOURCE)
@@ -225,7 +241,24 @@ public final class UwbManager {
*/
@AngleOfArrivalSupportType
public int getAngleOfArrivalSupport() {
throw new UnsupportedOperationException();
try {
switch (mUwbAdapter.getAngleOfArrivalSupport()) {
case AngleOfArrivalSupport.TWO_DIMENSIONAL:
return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D;
case AngleOfArrivalSupport.THREE_DIMENSIONAL_HEMISPHERICAL:
return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL;
case AngleOfArrivalSupport.THREE_DIMENSIONAL_SPHERICAL:
return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL;
case AngleOfArrivalSupport.NONE:
default:
return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE;
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
@@ -239,7 +272,15 @@ public final class UwbManager {
*/
@NonNull
public List<Integer> getSupportedChannelNumbers() {
throw new UnsupportedOperationException();
List<Integer> channels = new ArrayList<>();
try {
for (int channel : mUwbAdapter.getSupportedChannels()) {
channels.add(channel);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return channels;
}
/**
@@ -250,7 +291,15 @@ public final class UwbManager {
*/
@NonNull
public Set<Integer> getSupportedPreambleCodeIndices() {
throw new UnsupportedOperationException();
Set<Integer> preambles = new HashSet<>();
try {
for (int preamble : mUwbAdapter.getSupportedPreambleCodes()) {
preambles.add(preamble);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return preambles;
}
/**
@@ -262,7 +311,11 @@ public final class UwbManager {
*/
@SuppressLint("MethodNameUnits")
public long elapsedRealtimeResolutionNanos() {
throw new UnsupportedOperationException();
try {
return mUwbAdapter.getTimestampResolutionNanos();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
@@ -271,7 +324,11 @@ public final class UwbManager {
* @return the maximum allowed number of simultaneously open {@link RangingSession} instances.
*/
public int getMaxSimultaneousSessions() {
throw new UnsupportedOperationException();
try {
return mUwbAdapter.getMaxSimultaneousSessions();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
@@ -281,7 +338,11 @@ public final class UwbManager {
* @return the maximum number of remote devices per {@link RangingSession}
*/
public int getMaxRemoteDevicesPerInitiatorSession() {
throw new UnsupportedOperationException();
try {
return mUwbAdapter.getMaxRemoteDevicesPerInitiatorSession();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
@@ -291,7 +352,11 @@ public final class UwbManager {
* @return the maximum number of remote devices per {@link RangingSession}
*/
public int getMaxRemoteDevicesPerResponderSession() {
throw new UnsupportedOperationException();
try {
return mUwbAdapter.getMaxRemoteDevicesPerResponderSession();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**

View File

@@ -17,6 +17,7 @@ android_test {
static_libs: [
"androidx.test.ext.junit",
"androidx.test.rules",
"mockito-target-minus-junit4",
],
libs: [
"android.test.runner",

View File

@@ -0,0 +1,311 @@
/*
* Copyright 2020 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.uwb;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
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.os.RemoteException;
import android.uwb.UwbManager.AdapterStateCallback;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Test of {@link AdapterStateListener}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AdapterStateListenerTest {
IUwbAdapter mUwbAdapter = mock(IUwbAdapter.class);
Answer mRegisterSuccessAnswer = new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
IUwbAdapterStateCallbacks cb = (IUwbAdapterStateCallbacks) args[0];
try {
cb.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
} catch (RemoteException e) {
// Nothing to do
}
return new Object();
}
};
Throwable mThrowRemoteException = new RemoteException("RemoteException");
private static Executor getExecutor() {
return new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
}
private static void verifyCallbackStateChangedInvoked(
AdapterStateCallback callback, int numTimes) {
verify(callback, times(numTimes)).onStateChanged(anyBoolean(), anyInt());
}
@Test
public void testRegister_RegisterUnregister() throws RemoteException {
doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback1 = mock(AdapterStateCallback.class);
AdapterStateCallback callback2 = mock(AdapterStateCallback.class);
// Verify that the adapter state listener registered with the UWB Adapter
adapterStateListener.register(getExecutor(), callback1);
verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
verifyCallbackStateChangedInvoked(callback1, 1);
verifyCallbackStateChangedInvoked(callback2, 0);
// Register a second client and no new call to UWB Adapter
adapterStateListener.register(getExecutor(), callback2);
verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
verifyCallbackStateChangedInvoked(callback1, 1);
verifyCallbackStateChangedInvoked(callback2, 1);
// Unregister first callback
adapterStateListener.unregister(callback1);
verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
verify(mUwbAdapter, times(0)).unregisterAdapterStateCallbacks(any());
verifyCallbackStateChangedInvoked(callback1, 1);
verifyCallbackStateChangedInvoked(callback2, 1);
// Unregister second callback
adapterStateListener.unregister(callback2);
verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
verify(mUwbAdapter, times(1)).unregisterAdapterStateCallbacks(any());
verifyCallbackStateChangedInvoked(callback1, 1);
verifyCallbackStateChangedInvoked(callback2, 1);
}
@Test
public void testRegister_FirstRegisterFails() throws RemoteException {
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback1 = mock(AdapterStateCallback.class);
AdapterStateCallback callback2 = mock(AdapterStateCallback.class);
// Throw a remote exception whenever first registering
doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any());
adapterStateListener.register(getExecutor(), callback1);
verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
// No longer throw an exception, instead succeed
doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
// Register a different callback
adapterStateListener.register(getExecutor(), callback2);
verify(mUwbAdapter, times(2)).registerAdapterStateCallbacks(any());
// Ensure first callback was invoked again
verifyCallbackStateChangedInvoked(callback1, 2);
verifyCallbackStateChangedInvoked(callback2, 1);
}
@Test
public void testRegister_RegisterSameCallbackTwice() throws RemoteException {
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback = mock(AdapterStateCallback.class);
doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
adapterStateListener.register(getExecutor(), callback);
verifyCallbackStateChangedInvoked(callback, 1);
adapterStateListener.register(getExecutor(), callback);
verifyCallbackStateChangedInvoked(callback, 1);
// Invoke a state change and ensure the callback is only called once
adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
verifyCallbackStateChangedInvoked(callback, 2);
}
@Test
public void testCallback_RunViaExecutor_Success() throws RemoteException {
// Verify that the callbacks are invoked on the executor when successful
doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
runViaExecutor();
}
@Test
public void testCallback_RunViaExecutor_Failure() throws RemoteException {
// Verify that the callbacks are invoked on the executor when there is a remote exception
doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any());
runViaExecutor();
}
private void runViaExecutor() {
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback = mock(AdapterStateCallback.class);
Executor executor = mock(Executor.class);
// Do not run commands received and ensure that the callback is not invoked
doAnswer(new ExecutorAnswer(false)).when(executor).execute(any());
adapterStateListener.register(executor, callback);
verify(executor, times(1)).execute(any());
verifyCallbackStateChangedInvoked(callback, 0);
// Manually invoke the callback and ensure callback is not invoked
adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
verify(executor, times(2)).execute(any());
verifyCallbackStateChangedInvoked(callback, 0);
// Run the command that the executor receives
doAnswer(new ExecutorAnswer(true)).when(executor).execute(any());
adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
verify(executor, times(3)).execute(any());
verifyCallbackStateChangedInvoked(callback, 1);
}
class ExecutorAnswer implements Answer {
final boolean mShouldRun;
ExecutorAnswer(boolean shouldRun) {
mShouldRun = shouldRun;
}
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (mShouldRun) {
((Runnable) invocation.getArgument(0)).run();
}
return null;
}
}
@Test
public void testNotify_AllCallbacksNotified() throws RemoteException {
doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
List<AdapterStateCallback> callbacks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
AdapterStateCallback callback = mock(AdapterStateCallback.class);
adapterStateListener.register(getExecutor(), callback);
callbacks.add(callback);
}
// Ensure every callback got the initial state
for (AdapterStateCallback callback : callbacks) {
verifyCallbackStateChangedInvoked(callback, 1);
}
// Invoke a state change and ensure all callbacks are invoked
adapterStateListener.onAdapterStateChanged(true, StateChangeReason.ALL_SESSIONS_CLOSED);
for (AdapterStateCallback callback : callbacks) {
verifyCallbackStateChangedInvoked(callback, 2);
}
}
@Test
public void testStateChange_CorrectValue() {
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback = mock(AdapterStateCallback.class);
adapterStateListener.register(getExecutor(), callback);
runStateChangeValue(StateChangeReason.ALL_SESSIONS_CLOSED,
AdapterStateCallback.STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED);
runStateChangeValue(StateChangeReason.SESSION_STARTED,
AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED);
runStateChangeValue(StateChangeReason.SYSTEM_BOOT,
AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT);
runStateChangeValue(StateChangeReason.SYSTEM_POLICY,
AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY);
runStateChangeValue(StateChangeReason.UNKNOWN,
AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN);
}
private void runStateChangeValue(@StateChangeReason int reasonIn,
@AdapterStateCallback.StateChangedReason int reasonOut) {
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback = mock(AdapterStateCallback.class);
adapterStateListener.register(getExecutor(), callback);
adapterStateListener.onAdapterStateChanged(false, reasonIn);
verify(callback, times(1)).onStateChanged(false, reasonOut);
adapterStateListener.onAdapterStateChanged(true, reasonIn);
verify(callback, times(1)).onStateChanged(true, reasonOut);
}
@Test
public void testStateChange_FirstRegisterGetsCorrectState() throws RemoteException {
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback = mock(AdapterStateCallback.class);
Answer registerAnswer = new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
IUwbAdapterStateCallbacks cb = (IUwbAdapterStateCallbacks) args[0];
try {
cb.onAdapterStateChanged(true, StateChangeReason.SESSION_STARTED);
} catch (RemoteException e) {
// Nothing to do
}
return new Object();
}
};
doAnswer(registerAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
adapterStateListener.register(getExecutor(), callback);
verify(callback).onStateChanged(true,
AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED);
}
@Test
public void testStateChange_SecondRegisterGetsCorrectState() {
AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
AdapterStateCallback callback1 = mock(AdapterStateCallback.class);
AdapterStateCallback callback2 = mock(AdapterStateCallback.class);
adapterStateListener.register(getExecutor(), callback1);
adapterStateListener.onAdapterStateChanged(true, StateChangeReason.SYSTEM_BOOT);
adapterStateListener.register(getExecutor(), callback2);
verify(callback2).onStateChanged(true,
AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT);
}
}