From 84fa27056bd7ef04064ec1b765572bcf3cb8dde9 Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Thu, 12 Sep 2019 13:39:42 -0400 Subject: [PATCH] Add new ProximitySensor wrapper that automatically uses appropriate sensor. Bug: 140270231 Test: manual && atest SystemUITests Change-Id: Id151ce8f702f7deadab73ee3904a0a883870b237 --- .../classifier/FalsingManagerProxy.java | 13 +- .../brightline/BrightLineFalsingManager.java | 46 +---- .../brightline/FalsingClassifier.java | 6 +- .../brightline/ProximityClassifier.java | 17 +- .../systemui/util/ProximitySensor.java | 186 ++++++++++++++++++ .../classifier/FalsingManagerProxyTest.java | 14 +- .../brightline/ProximityClassifierTest.java | 53 ++--- 7 files changed, 240 insertions(+), 95 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index c9f5b79918f82..85bc22bab36f5 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -34,8 +34,8 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.util.AsyncSensorManager; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.ProximitySensor; import java.io.PrintWriter; @@ -51,6 +51,9 @@ import javax.inject.Singleton; @Singleton public class FalsingManagerProxy implements FalsingManager { + private static final String PROXIMITY_SENSOR_TAG = "FalsingManager"; + + private final ProximitySensor mProximitySensor; private FalsingManager mInternalFalsingManager; private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener; private final DeviceConfigProxy mDeviceConfig; @@ -58,7 +61,11 @@ public class FalsingManagerProxy implements FalsingManager { @Inject FalsingManagerProxy(Context context, PluginManager pluginManager, - @Named(MAIN_HANDLER_NAME) Handler handler, DeviceConfigProxy deviceConfig) { + @Named(MAIN_HANDLER_NAME) Handler handler, + ProximitySensor proximitySensor, + DeviceConfigProxy deviceConfig) { + mProximitySensor = proximitySensor; + mProximitySensor.setTag(PROXIMITY_SENSOR_TAG); mDeviceConfig = deviceConfig; mDeviceConfigListener = properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace()); @@ -113,8 +120,8 @@ public class FalsingManagerProxy implements FalsingManager { } else { mInternalFalsingManager = new BrightLineFalsingManager( new FalsingDataProvider(context.getResources().getDisplayMetrics()), - Dependency.get(AsyncSensorManager.class), Dependency.get(KeyguardUpdateMonitor.class), + mProximitySensor, mDeviceConfig ); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java index 9e646b627bd23..3f5cae678d016 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java @@ -19,10 +19,6 @@ package com.android.systemui.classifier.brightline; import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_REMAIN_LOCKED; import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_SUCCESS; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; import android.hardware.biometrics.BiometricSourceType; import android.net.Uri; import android.util.Log; @@ -34,12 +30,11 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.classifier.Classifier; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.ProximitySensor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; /** * FalsingManager designed to make clear why a touch was rejected. @@ -49,9 +44,9 @@ public class BrightLineFalsingManager implements FalsingManager { static final boolean DEBUG = false; private static final String TAG = "FalsingManagerPlugin"; - private final SensorManager mSensorManager; private final FalsingDataProvider mDataProvider; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final ProximitySensor mProximitySensor; private boolean mSessionStarted; private MetricsLogger mMetricsLogger; private int mIsFalseTouchCalls; @@ -59,20 +54,9 @@ public class BrightLineFalsingManager implements FalsingManager { private boolean mScreenOn; private boolean mJustUnlockedWithFace; - private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor(); - private final List mClassifiers; - private SensorEventListener mSensorEventListener = new SensorEventListener() { - @Override - public synchronized void onSensorChanged(SensorEvent event) { - onSensorEvent(event); - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - }; + private ProximitySensor.ProximitySensorListener mSensorEventListener = this::onProximityEvent; private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -88,12 +72,12 @@ public class BrightLineFalsingManager implements FalsingManager { public BrightLineFalsingManager( FalsingDataProvider falsingDataProvider, - SensorManager sensorManager, KeyguardUpdateMonitor keyguardUpdateMonitor, + ProximitySensor proximitySensor, DeviceConfigProxy deviceConfigProxy) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mDataProvider = falsingDataProvider; - mSensorManager = sensorManager; + mProximitySensor = proximitySensor; mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); mMetricsLogger = new MetricsLogger(); @@ -111,24 +95,12 @@ public class BrightLineFalsingManager implements FalsingManager { } private void registerSensors() { - Sensor s = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - if (s != null) { - // This can be expensive, and doesn't need to happen on the main thread. - mBackgroundExecutor.submit(() -> { - logDebug("registering sensor listener"); - mSensorManager.registerListener( - mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME); - }); - } + mProximitySensor.register(mSensorEventListener); } private void unregisterSensors() { - // This can be expensive, and doesn't need to happen on the main thread. - mBackgroundExecutor.submit(() -> { - logDebug("unregistering sensor listener"); - mSensorManager.unregisterListener(mSensorEventListener); - }); + mProximitySensor.unregister(mSensorEventListener); } private void sessionStart() { @@ -190,10 +162,10 @@ public class BrightLineFalsingManager implements FalsingManager { mClassifiers.forEach((classifier) -> classifier.onTouchEvent(motionEvent)); } - private void onSensorEvent(SensorEvent sensorEvent) { + private void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) { // TODO: some of these classifiers might allow us to abort early, meaning we don't have to // make these calls. - mClassifiers.forEach((classifier) -> classifier.onSensorEvent(sensorEvent)); + mClassifiers.forEach((classifier) -> classifier.onProximityEvent(proximityEvent)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java index 685e7c534b660..bf397518de461 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java @@ -16,10 +16,10 @@ package com.android.systemui.classifier.brightline; -import android.hardware.SensorEvent; import android.view.MotionEvent; import com.android.systemui.classifier.Classifier; +import com.android.systemui.util.ProximitySensor; import java.util.List; @@ -98,9 +98,9 @@ abstract class FalsingClassifier { void onTouchEvent(MotionEvent motionEvent) {}; /** - * Called whenever a SensorEvent occurs, specifically the ProximitySensor. + * Called when a ProximityEvent occurs (change in near/far). */ - void onSensorEvent(SensorEvent sensorEvent) {}; + void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) {}; /** * The phone screen has turned on and we need to begin falsing detection. diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java index 1827047261290..eeca409866a89 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java @@ -19,12 +19,11 @@ package com.android.systemui.classifier.brightline; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; -import android.hardware.Sensor; -import android.hardware.SensorEvent; import android.provider.DeviceConfig; import android.view.MotionEvent; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.ProximitySensor; /** @@ -99,14 +98,12 @@ class ProximityClassifier extends FalsingClassifier { } @Override - public void onSensorEvent(SensorEvent sensorEvent) { - if (sensorEvent.sensor.getType() == Sensor.TYPE_PROXIMITY) { - logDebug("Sensor is: " + (sensorEvent.values[0] < sensorEvent.sensor.getMaximumRange()) - + " at time " + sensorEvent.timestamp); - update( - sensorEvent.values[0] < sensorEvent.sensor.getMaximumRange(), - sensorEvent.timestamp); - } + public void onProximityEvent( + ProximitySensor.ProximityEvent proximityEvent) { + boolean near = proximityEvent.getNear(); + long timestampNs = proximityEvent.getTimestampNs(); + logDebug("Sensor is: " + near + " at time " + timestampNs); + update(near, timestampNs); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java new file mode 100644 index 0000000000000..a905eba1f0ed7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/ProximitySensor.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2019 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 com.android.systemui.util; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.Log; + +import com.android.systemui.R; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +/** + * Simple wrapper around SensorManager customized for the Proximity sensor. + */ +public class ProximitySensor { + private static final String TAG = "ProxSensor"; + private static final boolean DEBUG = false; + + private final Sensor mSensor; + private final AsyncSensorManager mSensorManager; + private final boolean mUsingBrightnessSensor; + private final float mMaxRange; + + private SensorEventListener mSensorEventListener = new SensorEventListener() { + @Override + public synchronized void onSensorChanged(SensorEvent event) { + onSensorEvent(event); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + private boolean mNear; + private List mListeners = new ArrayList<>(); + private String mTag = null; + + @Inject + public ProximitySensor(Context context, AsyncSensorManager sensorManager) { + mSensorManager = sensorManager; + Sensor sensor = findBrightnessSensor(context, sensorManager); + + if (sensor == null) { + mUsingBrightnessSensor = false; + sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + } else { + mUsingBrightnessSensor = true; + } + mSensor = sensor; + if (mSensor != null) { + mMaxRange = mSensor.getMaximumRange(); + } else { + mMaxRange = 0; + } + } + + public void setTag(String tag) { + mTag = tag; + } + + private Sensor findBrightnessSensor(Context context, SensorManager sensorManager) { + String sensorType = context.getString(R.string.doze_brightness_sensor_type); + List sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL); + Sensor sensor = null; + for (Sensor s : sensorList) { + if (sensorType.equals(s.getStringType())) { + sensor = s; + break; + } + } + + return sensor; + } + + /** + * Returns {@code false} if a Proximity sensor is not available. + */ + public boolean getSensorAvailable() { + return mSensor != null; + } + + /** + * Add a listener. + * + * Registers itself with the {@link SensorManager} if this is the first listener + * added. + */ + public boolean register(ProximitySensorListener listener) { + if (!getSensorAvailable()) { + return false; + } + + logDebug("using brightness sensor? " + mUsingBrightnessSensor); + mListeners.add(listener); + if (mListeners.size() == 1) { + logDebug("registering sensor listener"); + mSensorManager.registerListener( + mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_GAME); + } + + return true; + } + + /** + * Remove a listener. + * + * If all listeners are removed from an instance of this class, + * it will unregister itself with the SensorManager. + */ + public void unregister(ProximitySensorListener listener) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + logDebug("unregistering sensor listener"); + mSensorManager.unregisterListener(mSensorEventListener); + } + } + + public boolean isNear() { + return getSensorAvailable() && mNear; + } + + private void onSensorEvent(SensorEvent event) { + boolean near = event.values[0] < mMaxRange; + if (mUsingBrightnessSensor) { + near = event.values[0] == 0; + } + mNear = near; + mListeners.forEach(proximitySensorListener -> + proximitySensorListener.onProximitySensorEvent( + new ProximityEvent(mNear, event.timestamp))); + } + + /** Implement to be notified of ProximityEvents. */ + public interface ProximitySensorListener { + /** Called when the ProximitySensor changes. */ + void onProximitySensorEvent(ProximityEvent proximityEvent); + } + + /** + * Returned when the near/far state of a {@link ProximitySensor} changes. + */ + public static class ProximityEvent { + private final boolean mNear; + private final long mTimestampNs; + + public ProximityEvent(boolean near, long timestampNs) { + mNear = near; + mTimestampNs = timestampNs; + } + + public boolean getNear() { + return mNear; + } + + public long getTimestampNs() { + return mTimestampNs; + } + } + + private void logDebug(String msg) { + if (DEBUG) { + Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java index e1457ef28e363..b9793562d8d81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java @@ -34,6 +34,7 @@ import com.android.systemui.classifier.brightline.BrightLineFalsingManager; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; +import com.android.systemui.util.ProximitySensor; import org.junit.After; import org.junit.Before; @@ -46,8 +47,10 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class FalsingManagerProxyTest extends SysuiTestCase { - @Mock + @Mock(stubOnly = true) PluginManager mPluginManager; + @Mock(stubOnly = true) + ProximitySensor mProximitySensor; private Handler mHandler; private FalsingManagerProxy mProxy; private DeviceConfigProxy mDeviceConfig; @@ -73,7 +76,8 @@ public class FalsingManagerProxyTest extends SysuiTestCase { @Test public void test_brightLineFalsingManagerDisabled() { - mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mDeviceConfig); + mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor, + mDeviceConfig); assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class)); } @@ -82,13 +86,15 @@ public class FalsingManagerProxyTest extends SysuiTestCase { mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false); mTestableLooper.processAllMessages(); - mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mDeviceConfig); + mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor, + mDeviceConfig); assertThat(mProxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class)); } @Test public void test_brightLineFalsingManagerToggled() throws InterruptedException { - mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mDeviceConfig); + mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor, + mDeviceConfig); assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class)); mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java index c76fe74b1af75..3fc5d7202d0ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java @@ -23,8 +23,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; -import android.hardware.Sensor; -import android.hardware.SensorEvent; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -32,17 +30,15 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; import com.android.systemui.util.DeviceConfigProxyFake; +import com.android.systemui.util.ProximitySensor; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.lang.reflect.Field; - @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -81,8 +77,8 @@ public class ProximityClassifierTest extends ClassifierTest { @Test public void testPass_mostlyUncovered() { touchDown(); - mClassifier.onSensorEvent(createSensorEvent(true, 1)); - mClassifier.onSensorEvent(createSensorEvent(false, 2)); + mClassifier.onProximityEvent(createSensorEvent(true, 1)); + mClassifier.onProximityEvent(createSensorEvent(false, 2)); touchUp(20); assertThat(mClassifier.isFalseTouch(), is(false)); } @@ -91,8 +87,8 @@ public class ProximityClassifierTest extends ClassifierTest { public void testPass_quickSettings() { touchDown(); when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS); - mClassifier.onSensorEvent(createSensorEvent(true, 1)); - mClassifier.onSensorEvent(createSensorEvent(false, 11)); + mClassifier.onProximityEvent(createSensorEvent(true, 1)); + mClassifier.onProximityEvent(createSensorEvent(false, 11)); touchUp(10); assertThat(mClassifier.isFalseTouch(), is(false)); } @@ -100,8 +96,8 @@ public class ProximityClassifierTest extends ClassifierTest { @Test public void testFail_covered() { touchDown(); - mClassifier.onSensorEvent(createSensorEvent(true, 1)); - mClassifier.onSensorEvent(createSensorEvent(false, 11)); + mClassifier.onProximityEvent(createSensorEvent(true, 1)); + mClassifier.onProximityEvent(createSensorEvent(false, 11)); touchUp(10); assertThat(mClassifier.isFalseTouch(), is(true)); } @@ -109,10 +105,10 @@ public class ProximityClassifierTest extends ClassifierTest { @Test public void testFail_mostlyCovered() { touchDown(); - mClassifier.onSensorEvent(createSensorEvent(true, 1)); - mClassifier.onSensorEvent(createSensorEvent(true, 95)); - mClassifier.onSensorEvent(createSensorEvent(true, 96)); - mClassifier.onSensorEvent(createSensorEvent(false, 100)); + mClassifier.onProximityEvent(createSensorEvent(true, 1)); + mClassifier.onProximityEvent(createSensorEvent(true, 95)); + mClassifier.onProximityEvent(createSensorEvent(true, 96)); + mClassifier.onProximityEvent(createSensorEvent(false, 100)); touchUp(100); assertThat(mClassifier.isFalseTouch(), is(true)); } @@ -120,8 +116,8 @@ public class ProximityClassifierTest extends ClassifierTest { @Test public void testPass_coveredWithLongSwipe() { touchDown(); - mClassifier.onSensorEvent(createSensorEvent(true, 1)); - mClassifier.onSensorEvent(createSensorEvent(false, 11)); + mClassifier.onProximityEvent(createSensorEvent(true, 1)); + mClassifier.onProximityEvent(createSensorEvent(false, 11)); touchUp(10); when(mDistanceClassifier.isLongSwipe()).thenReturn(true); assertThat(mClassifier.isFalseTouch(), is(false)); @@ -142,26 +138,7 @@ public class ProximityClassifierTest extends ClassifierTest { motionEvent.recycle(); } - private SensorEvent createSensorEvent(boolean covered, long timestampMs) { - SensorEvent sensorEvent = Mockito.mock(SensorEvent.class); - Sensor sensor = Mockito.mock(Sensor.class); - when(sensor.getType()).thenReturn(Sensor.TYPE_PROXIMITY); - when(sensor.getMaximumRange()).thenReturn(1f); - sensorEvent.sensor = sensor; - sensorEvent.timestamp = timestampMs * NS_PER_MS; - try { - Field valuesField = SensorEvent.class.getField("values"); - valuesField.setAccessible(true); - float[] sensorValue = {covered ? 0 : 1}; - try { - valuesField.set(sensorEvent, sensorValue); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } - - return sensorEvent; + private ProximitySensor.ProximityEvent createSensorEvent(boolean covered, long timestampMs) { + return new ProximitySensor.ProximityEvent(covered, timestampMs * NS_PER_MS); } }