/*
* Copyright (C) 2015 Google Inc.
*
* 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.talkback;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.hardware.SensorManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.view.accessibility.AccessibilityEvent;
import com.android.talkback.controller.GestureController;
import com.android.utils.SharedPreferencesUtils;
import com.android.utils.picidae.IntegratedTapDetector;
import com.google.android.marvin.talkback.TalkBackService;
import com.googlecode.eyesfree.testing.TalkBackInstrumentationTestCase;
/**
* Tests for SideTapManager
*/
public class SideTapManagerTest extends TalkBackInstrumentationTestCase {
private TalkBackService mTalkBack;
private SideTapManager mSideTapManager;
private MockGestureController mGestureController;
private Instrumentation mInstrumentation;
private MockIntegratedTapDetector mMockIntegratedDetector;
@Override
protected void setUp() throws Exception {
super.setUp();
mTalkBack = getService();
waitForAccessibilityIdleSync();
mGestureController = new MockGestureController();
mSideTapManager = new SideTapManager(mTalkBack, mGestureController);
mMockIntegratedDetector = new MockIntegratedTapDetector(
(SensorManager) mTalkBack.getSystemService(Context.SENSOR_SERVICE));
mSideTapManager.setIntegratedTapDetector(mMockIntegratedDetector);
mMockIntegratedDetector.singleTapQuality = -1.0;
mMockIntegratedDetector.doubleTapQuality = -1.0;
mInstrumentation = getInstrumentation();
}
@Override
protected void tearDown() throws Exception {
// Must set prefs before teardown because TalkBack service is required.
setSingleTapPreference(R.string.shortcut_value_unassigned);
setDoubleTapPreference(R.string.shortcut_value_unassigned);
super.tearDown();
}
private void setSingleTapPreference(int stringId) {
SharedPreferences settings = SharedPreferencesUtils.getSharedPreferences(mTalkBack);
settings.edit().putString(mTalkBack.getString(R.string.pref_shortcut_single_tap_key),
mTalkBack.getString(stringId)).commit();
}
private void setDoubleTapPreference(int stringId) {
SharedPreferences settings = SharedPreferencesUtils.getSharedPreferences(mTalkBack);
settings.edit().putString(mTalkBack.getString(R.string.pref_shortcut_double_tap_key),
mTalkBack.getString(stringId)).commit();
}
private void setTapSensitivityPreference(int stringId) {
SharedPreferences settings = SharedPreferencesUtils.getSharedPreferences(mTalkBack);
settings.edit().putString(mTalkBack.getString(R.string.pref_tap_sensitivity_key),
mTalkBack.getString(stringId)).commit();
}
@MediumTest
public void testReloadPreferences_singleTap() {
setSingleTapPreference(R.string.shortcut_value_home);
setDoubleTapPreference(R.string.shortcut_value_unassigned);
mMockIntegratedDetector.doubleTapSpacing = -1;
mSideTapManager.onReloadPreferences();
assertEquals(1, mMockIntegratedDetector.startCount);
assertEquals(0, mMockIntegratedDetector.stopCount);
assertEquals(0, mMockIntegratedDetector.doubleTapSpacing);
}
@MediumTest
public void testReloadPreferences_doubleTap() {
setDoubleTapPreference(R.string.shortcut_value_home);
setSingleTapPreference(R.string.shortcut_value_unassigned);
mMockIntegratedDetector.doubleTapSpacing = -1;
mSideTapManager.onReloadPreferences();
assertEquals(1, mMockIntegratedDetector.startCount);
assertEquals(0, mMockIntegratedDetector.stopCount);
assertTrue(mMockIntegratedDetector.doubleTapSpacing > 0);
}
@MediumTest
public void testReloadPreferences_tapsUnassigned() {
setDoubleTapPreference(R.string.shortcut_value_unassigned);
setSingleTapPreference(R.string.shortcut_value_unassigned);
mMockIntegratedDetector.doubleTapSpacing = -1;
mSideTapManager.onReloadPreferences();
assertEquals(0, mMockIntegratedDetector.startCount);
assertEquals(1, mMockIntegratedDetector.stopCount);
assertEquals(0, mMockIntegratedDetector.doubleTapSpacing);
}
@MediumTest
public void testReloadPreferences_sensitivityLowest() {
setTapSensitivityPreference(R.string.tap_sensitivity_value_lowest);
mSideTapManager.onReloadPreferences();
assertEquals(IntegratedTapDetector.TAP_QUALITY_HIGHEST,
mMockIntegratedDetector.singleTapQuality, 0.001);
assertEquals(IntegratedTapDetector.TAP_QUALITY_LOW,
mMockIntegratedDetector.doubleTapQuality, 0.001);
}
@MediumTest
public void testReloadPreferences_sensitivityLow() {
setTapSensitivityPreference(R.string.tap_sensitivity_value_low);
mSideTapManager.onReloadPreferences();
assertEquals(IntegratedTapDetector.TAP_QUALITY_HIGH,
mMockIntegratedDetector.singleTapQuality, 0.001);
assertEquals(IntegratedTapDetector.TAP_QUALITY_LOW,
mMockIntegratedDetector.doubleTapQuality, 0.001);
}
@MediumTest
public void testReloadPreferences_sensitivityMedium() {
setTapSensitivityPreference(R.string.tap_sensitivity_value_medium);
mSideTapManager.onReloadPreferences();
assertEquals(IntegratedTapDetector.TAP_QUALITY_MEDIUM,
mMockIntegratedDetector.singleTapQuality, 0.001);
assertEquals(IntegratedTapDetector.TAP_QUALITY_LOW,
mMockIntegratedDetector.doubleTapQuality, 0.001);
}
@MediumTest
public void testReloadPreferences_sensitivityHigh() {
setTapSensitivityPreference(R.string.tap_sensitivity_value_high);
mSideTapManager.onReloadPreferences();
assertEquals(IntegratedTapDetector.TAP_QUALITY_LOW,
mMockIntegratedDetector.singleTapQuality, 0.001);
assertEquals(IntegratedTapDetector.TAP_QUALITY_LOW,
mMockIntegratedDetector.doubleTapQuality, 0.001);
}
@MediumTest
public void testOnSuspendInfrastructure() {
mSideTapManager.onSuspendInfrastructure();
assertEquals(1, mMockIntegratedDetector.stopCount);
}
/* Simulate a screen touch by sending an accessibilityEvent to the sideTapManager */
private void touchScreen() {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
mSideTapManager.onAccessibilityEvent(event);
}
@MediumTest
public void testSingleTaps_tapSentToTalkBack() {
/* Single Tap at a legal time */
touchScreen();
long startTime = System.nanoTime();
mSideTapManager.onHapticFeedbackStarting(startTime);
mSideTapManager.onSingleTap(startTime + 2000 * 1000 * 1000L);
assertEquals(1, mGestureController.mNumPerformCustomGestureCalls);
SharedPreferences prefs = SharedPreferencesUtils.getSharedPreferences(mTalkBack);
String expectedAction = prefs.getString(
mTalkBack.getString(R.string.pref_shortcut_single_tap_key),
mTalkBack.getString(R.string.pref_shortcut_single_tap_default));
assertEquals(expectedAction, mGestureController.mLastAction);
/* Tap should not be sent along if service is not active */
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mTalkBack.suspendTalkBack();
}
});
mInstrumentation.waitForIdleSync();
waitForAccessibilityIdleSync();
mSideTapManager.onSingleTap(startTime + 4000 * 1000 * 1000L);
assertEquals(1, mGestureController.mNumPerformCustomGestureCalls);
/* Make sure it works again after resume */
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mTalkBack.resumeTalkBack();
}
});
mInstrumentation.waitForIdleSync();
waitForAccessibilityIdleSync();
mSideTapManager.onSingleTap(startTime + 6000 * 1000 * 1000L);
assertEquals(2, mGestureController.mNumPerformCustomGestureCalls);
}
@MediumTest
public void testSingleTaps_ignoreTapsAfterScreenTouch() {
/* Single Tap 10 ms after a screen touch */
touchScreen();
long startTime = System.nanoTime();
mSideTapManager.onHapticFeedbackStarting(startTime - 2000 * 1000 * 1000L);
mSideTapManager.onSingleTap(startTime + 10 * 1000 * 1000L);
assertEquals(0, mGestureController.mNumPerformCustomGestureCalls);
}
@MediumTest
public void testSingleTaps_ignoreTapsAfterHaptic() {
/* Single Tap 10 ms after haptic */
touchScreen();
long startTime = System.nanoTime();
mSideTapManager.onHapticFeedbackStarting(startTime + 1990 * 1000 * 1000L);
mSideTapManager.onSingleTap(startTime + 2000 * 1000 * 1000L);
assertEquals(0, mGestureController.mNumPerformCustomGestureCalls);
}
@MediumTest
public void testDoubleTaps_tapSentToTalkBack() {
/* Double Tap at a legal time */
touchScreen();
long startTime = System.nanoTime();
mSideTapManager.onHapticFeedbackStarting(startTime);
mSideTapManager.onDoubleTap(startTime + 2000 * 1000 * 1000L);
assertEquals(1, mGestureController.mNumPerformCustomGestureCalls);
SharedPreferences prefs = SharedPreferencesUtils.getSharedPreferences(mTalkBack);
String expectedAction = prefs.getString(
mTalkBack.getString(R.string.pref_shortcut_double_tap_key),
mTalkBack.getString(R.string.pref_shortcut_double_tap_default));
assertEquals(expectedAction, mGestureController.mLastAction);
/* Tap should not be sent along if service is not active */
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mTalkBack.suspendTalkBack();
}
});
mInstrumentation.waitForIdleSync();
waitForAccessibilityIdleSync();
mSideTapManager.onDoubleTap(startTime + 4000 * 1000 * 1000L);
assertEquals(1, mGestureController.mNumPerformCustomGestureCalls);
/* Make sure it works again after resume */
mInstrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
mTalkBack.resumeTalkBack();
}
});
mInstrumentation.waitForIdleSync();
waitForAccessibilityIdleSync();
mSideTapManager.onDoubleTap(startTime + 6000 * 1000 * 1000L);
assertEquals(2, mGestureController.mNumPerformCustomGestureCalls);
}
@MediumTest
public void testDoubleTaps_ignoreTapsAfterScreenTouch() {
/* Single Tap 10 ms before a screen touch */
touchScreen();
long startTime = System.nanoTime();
mSideTapManager.onHapticFeedbackStarting(startTime - 2000 * 1000 * 1000L);
mSideTapManager.onDoubleTap(startTime - 10 * 1000 * 1000L);
assertEquals(0, mGestureController.mNumPerformCustomGestureCalls);
}
@MediumTest
public void testDoubleTaps_ignoreTapsAfterHaptic() {
/* Double Tap 10 ms after haptic */
touchScreen();
long startTime = System.nanoTime();
mSideTapManager.onHapticFeedbackStarting(startTime + 1990 * 1000 * 1000L);
mSideTapManager.onDoubleTap(startTime + 2000 * 1000 * 1000L);
assertEquals(0, mGestureController.mNumPerformCustomGestureCalls);
}
@MediumTest
public void testScreenTurnedOffAndOn_tapDetectorShouldStopAndStart() {
setSingleTapPreference(R.string.shortcut_value_home);
int startCountBefore = mMockIntegratedDetector.startCount;
int stopCountBefore = mMockIntegratedDetector.stopCount;
Intent screenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
Intent screenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mSideTapManager.onReceive(mTalkBack, screenOffIntent);
assertEquals(1, mMockIntegratedDetector.stopCount - stopCountBefore);
assertEquals(0, mMockIntegratedDetector.startCount - startCountBefore);
mSideTapManager.onReceive(mTalkBack, screenOnIntent);
assertEquals(1, mMockIntegratedDetector.stopCount - stopCountBefore);
assertEquals(1, mMockIntegratedDetector.startCount - startCountBefore);
}
}
/*
* TODO I'd prefer to use mockito here, but I get confusing
* error messages that the class can't be constructed, so I'm creating these
* mocks myself.
*/
final class MockIntegratedTapDetector extends IntegratedTapDetector {
public int startCount = 0, stopCount = 0;
public long messageDelay;
public long doubleTapSpacing;
public double singleTapQuality;
public double doubleTapQuality;
public MockIntegratedTapDetector(SensorManager sensorManager) {
super(sensorManager);
}
@Override
public void start() {
startCount++;
}
@Override
public void stop() {
stopCount++;
}
@Override
public void setMaxDoubleTapSpacingNanos(long maxDTapSpacing) {
doubleTapSpacing = maxDTapSpacing;
}
@Override
public void setTapDetectionQuality(double tq) {
singleTapQuality = tq;
}
@Override
public void setDoubleTapDetectionQuality(double tq) {
doubleTapQuality = tq;
}
}
final class MockGestureController implements GestureController {
public int mNumPerformCustomGestureCalls = 0;
public String mLastAction;
@Override
public void onGesture(int gestureId) {
mNumPerformCustomGestureCalls++;
}
@Override
public void performAction(String action) {
mNumPerformCustomGestureCalls++;
mLastAction = action;
}
@Override
public String gestureFromAction(String action) {
return "";
}
@Override
public String gestureDescriptionFromAction(String action) {
return "";
}
}