diff options
Diffstat (limited to 'services/tests')
8 files changed, 1985 insertions, 0 deletions
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index b07a10b..186b349 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -7,8 +7,10 @@ LOCAL_MODULE_TAGS := tests # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := easymocklib LOCAL_JAVA_LIBRARIES := android.test.runner services + LOCAL_PACKAGE_NAME := FrameworksServicesTests LOCAL_CERTIFICATE := platform diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 5ce109f..f115f42 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -23,6 +23,19 @@ <application> <uses-library android:name="android.test.runner" /> + + <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService"> + <intent-filter> + <action android:name="android.accessibilityservice.AccessibilityService"/> + </intent-filter> + </service> + + <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService"> + <intent-filter> + <action android:name="android.accessibilityservice.AccessibilityService"/> + </intent-filter> + </service> + </application> <instrumentation diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java new file mode 100644 index 0000000..2bc6825 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java @@ -0,0 +1,725 @@ +/* + * Copyright (C) 2010 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.server; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.Message; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; + +/** + * This test exercises the + * {@link com.android.server.AccessibilityManagerService} by mocking the + * {@link android.view.accessibility.AccessibilityManager} which talks to to the + * service. The service itself is interacting with the platform. Note: Testing + * the service in full isolation would require significant amount of work for + * mocking all system interactions. It would also require a lot of mocking code. + */ +public class AccessibilityManagerServiceTest extends AndroidTestCase { + + /** + * Timeout required for pending Binder calls or event processing to + * complete. + */ + private static final long TIMEOUT_BINDER_CALL = 100; + + /** + * Timeout in which we are waiting for the system to start the mock + * accessibility services. + */ + private static final long TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES = 300; + + /** + * Timeout used for testing that a service is notified only upon a + * notification timeout. + */ + private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300; + + /** + * The interface used to talk to the tested service. + */ + private IAccessibilityManager mManagerService; + + @Override + public void setContext(Context context) { + super.setContext(context); + if (MyFirstMockAccessibilityService.sComponentName == null) { + MyFirstMockAccessibilityService.sComponentName = new ComponentName( + context.getPackageName(), MyFirstMockAccessibilityService.class.getName()) + .flattenToShortString(); + } + if (MySecondMockAccessibilityService.sComponentName == null) { + MySecondMockAccessibilityService.sComponentName = new ComponentName( + context.getPackageName(), MySecondMockAccessibilityService.class.getName()) + .flattenToShortString(); + } + } + + /** + * Creates a new instance. + */ + public AccessibilityManagerServiceTest() { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + mManagerService = IAccessibilityManager.Stub.asInterface(iBinder); + } + + @LargeTest + public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception { + // make sure accessibility is disabled + ensureAccessibilityEnabled(mContext, false); + + // create a client mock instance + MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); + + // invoke the method under test + boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + + // check expected result + assertFalse("The client must be disabled since accessibility is disabled.", + enabledAccessibilityDisabled); + + // enable accessibility + ensureAccessibilityEnabled(mContext, true); + + // invoke the method under test + boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + + // check expected result + assertTrue("The client must be enabled since accessibility is enabled.", + enabledAccessibilityEnabled); + } + + @LargeTest + public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception { + // enable accessibility before registering the client + ensureAccessibilityEnabled(mContext, true); + + // create a client mock instance + MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); + + // invoke the method under test + boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + + // check expected result + assertTrue("The client must be enabled since accessibility is enabled.", + enabledAccessibilityEnabled); + + // disable accessibility + ensureAccessibilityEnabled(mContext, false); + + // invoke the method under test + boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + + // check expected result + assertFalse("The client must be disabled since accessibility is disabled.", + enabledAccessibilityDisabled); + } + + @LargeTest + public void testGetAccessibilityServicesList() throws Exception { + boolean firstMockServiceInstalled = false; + boolean secondMockServiceInstalled = false; + + String packageName = getContext().getPackageName(); + String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName(); + String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName(); + + // look for the two mock services + for (ServiceInfo serviceInfo : mManagerService.getAccessibilityServiceList()) { + if (packageName.equals(serviceInfo.packageName)) { + if (firstMockServiceClassName.equals(serviceInfo.name)) { + firstMockServiceInstalled = true; + } else if (secondMockServiceClassName.equals(serviceInfo.name)) { + secondMockServiceInstalled = true; + } + } + } + + // check expected result + assertTrue("First mock service must be installed", firstMockServiceInstalled); + assertTrue("Second mock service must be installed", secondMockServiceInstalled); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations + service.expectEvent(sentEvent); + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + sentEvent.setPackageName("no.service.registered.for.this.package"); + + // set expectations + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + service.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + + // set expectations + service.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_OneService_NotifivationAfterTimeout() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility service + ensureOnlyMockServicesEnabled(mContext, true, false); + + // configure the mock service + MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo(); + info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT; + service.setServiceInfo(info); + + // wait for the binder call to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate the first event to be sent + AccessibilityEvent firstEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(firstEvent); + + // create and populate the second event to be sent + AccessibilityEvent secondEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(secondEvent); + + // set expectations + service.expectEvent(secondEvent); + service.replay(); + + // send the events + mManagerService.sendAccessibilityEvent(firstEvent); + mManagerService.sendAccessibilityEvent(secondEvent); + + // wait for #sendAccessibilityEvent to reach the backing service + Thread.sleep(TIMEOUT_BINDER_CALL); + + try { + service.verify(); + fail("No events must be dispatched before the expiration of the notification timeout."); + } catch (IllegalStateException ise) { + /* expected */ + } + + // wait for the configured notification timeout to expire + Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(service); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo(); + firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo(); + secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC; + secondService.setServiceInfo(secondInfo); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.expectEvent(sentEvent); + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + firstInfo.flags = AccessibilityServiceInfo.DEFAULT; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.replay(); + + // set expectations for the second mock service + secondService.expectEvent(sentEvent); + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault() + throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + firstInfo.flags = AccessibilityServiceInfo.DEFAULT; + firstService.setServiceInfo(firstInfo); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo(); + secondInfo.flags = AccessibilityServiceInfo.DEFAULT; + secondService.setServiceInfo(firstInfo); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // create and populate an event to be sent + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + fullyPopulateDefaultAccessibilityEvent(sentEvent); + + // set expectations for the first mock service + firstService.expectEvent(sentEvent); + firstService.replay(); + + // set expectations for the second mock service + secondService.replay(); + + // send the event + mManagerService.sendAccessibilityEvent(sentEvent); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + @LargeTest + public void testInterrupt() throws Exception { + // set the accessibility setting value + ensureAccessibilityEnabled(mContext, true); + + // enable the mock accessibility services + ensureOnlyMockServicesEnabled(mContext, true, true); + + // configure the first mock service + MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance; + firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // configure the second mock service + MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance; + secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo()); + + // wait for the binder calls to #setService to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // set expectations for the first mock service + firstService.expectInterrupt(); + firstService.replay(); + + // set expectations for the second mock service + secondService.expectInterrupt(); + secondService.replay(); + + // call the method under test + mManagerService.interrupt(); + + // verify if all expected methods have been called + assertMockServiceVerifiedWithinTimeout(firstService); + assertMockServiceVerifiedWithinTimeout(secondService); + } + + /** + * Fully populates the {@link AccessibilityEvent} to marshal. + * + * @param sentEvent The event to populate. + */ + private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) { + sentEvent.setAddedCount(1); + sentEvent.setBeforeText("BeforeText"); + sentEvent.setChecked(true); + sentEvent.setClassName("foo.bar.baz.Class"); + sentEvent.setContentDescription("ContentDescription"); + sentEvent.setCurrentItemIndex(1); + sentEvent.setEnabled(true); + sentEvent.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED); + sentEvent.setEventTime(1000); + sentEvent.setFromIndex(1); + sentEvent.setFullScreen(true); + sentEvent.setItemCount(1); + sentEvent.setPackageName("foo.bar.baz"); + sentEvent.setParcelableData(Message.obtain(null, 1, null)); + sentEvent.setPassword(true); + sentEvent.setRemovedCount(1); + } + + /** + * This class is a mock {@link IAccessibilityManagerClient}. + */ + public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub { + boolean mIsEnabled; + + public void setEnabled(boolean enabled) { + mIsEnabled = enabled; + } + } + + /** + * Ensures accessibility is in a given state by writing the state to the + * settings and waiting until the accessibility manager service pick it up. + * + * @param context A context handle to access the settings. + * @param enabled The accessibility state to write to the settings. + * @throws Exception If any error occurs. + */ + private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception { + boolean isEnabled = (Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1 ? true : false); + + if (isEnabled == enabled) { + return; + } + + Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, + enabled ? 1 : 0); + + // wait the accessibility manager service to pick the change up + Thread.sleep(TIMEOUT_BINDER_CALL); + } + + /** + * Ensures the only {@link MockAccessibilityService}s with given component + * names are enabled by writing to the system settings and waiting until the + * accessibility manager service picks that up or the + * {@link #TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES} is exceeded. + * + * @param context A context handle to access the settings. + * @param firstMockServiceEnabled If the first mock accessibility service is enabled. + * @param secondMockServiceEnabled If the second mock accessibility service is enabled. + * @throws IllegalStateException If some of the requested for enabling mock services + * is not properly started. + * @throws Exception Exception If any error occurs. + */ + private void ensureOnlyMockServicesEnabled(Context context, boolean firstMockServiceEnabled, + boolean secondMockServiceEnabled) throws Exception { + String enabledServices = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + + StringBuilder servicesToEnable = new StringBuilder(); + if (firstMockServiceEnabled) { + servicesToEnable.append(MyFirstMockAccessibilityService.sComponentName).append(":"); + } + if (secondMockServiceEnabled) { + servicesToEnable.append(MySecondMockAccessibilityService.sComponentName).append(":"); + } + + if (servicesToEnable.equals(enabledServices)) { + return; + } + + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString()); + + // we have enabled the services of interest and need to wait until they + // are instantiated and started (if needed) and the system binds to them + boolean firstMockServiceOK = false; + boolean secondMockServiceOK = false; + long start = SystemClock.uptimeMillis(); + long pollingInterval = TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES / 6; + + while (SystemClock.uptimeMillis() - start < TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES) { + firstMockServiceOK = !firstMockServiceEnabled + || (MyFirstMockAccessibilityService.sInstance != null + && MyFirstMockAccessibilityService.sInstance.isSystemBoundAsClient()); + + secondMockServiceOK = !secondMockServiceEnabled + || (MySecondMockAccessibilityService.sInstance != null + && MySecondMockAccessibilityService.sInstance.isSystemBoundAsClient()); + + if (firstMockServiceOK && secondMockServiceOK) { + return; + } + + Thread.sleep(pollingInterval); + } + + StringBuilder message = new StringBuilder(); + message.append("Mock accessibility services not started or system not bound as a client: "); + if (!firstMockServiceOK) { + message.append(MyFirstMockAccessibilityService.sComponentName); + message.append(" "); + } + if (!secondMockServiceOK) { + message.append(MySecondMockAccessibilityService.sComponentName); + } + throw new IllegalStateException(message.toString()); + } + + /** + * Asserts the the mock accessibility service has been successfully verified + * (which is it has received the expected method calls with expected + * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is + * checked by polling upon small intervals. + * + * @param service The service to verify. + * @throws Exception If the verification has failed with exception after the + * {@link #TIMEOUT_BINDER_CALL}. + */ + private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service) + throws Exception { + Exception lastVerifyException = null; + long beginTime = SystemClock.uptimeMillis(); + long pollTmeout = TIMEOUT_BINDER_CALL / 5; + + // poll until the timeout has elapsed + while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) { + // sleep first since immediate call will always fail + try { + Thread.sleep(pollTmeout); + } catch (InterruptedException ie) { + /* ignore */ + } + // poll for verification and if this fails save the exception and + // keep polling + try { + service.verify(); + // reset so it does not accept more events + service.reset(); + return; + } catch (Exception e) { + lastVerifyException = e; + } + } + + // reset, we have already failed + service.reset(); + + // always not null + throw lastVerifyException; + } + + /** + * This class is the first mock {@link AccessibilityService}. + */ + public static class MyFirstMockAccessibilityService extends MockAccessibilityService { + + /** + * The service {@link ComponentName} flattened as a string. + */ + static String sComponentName; + + /** + * Handle to the service instance. + */ + static MyFirstMockAccessibilityService sInstance; + + /** + * Creates a new instance. + */ + public MyFirstMockAccessibilityService() { + sInstance = this; + } + } + + /** + * This class is the first mock {@link AccessibilityService}. + */ + public static class MySecondMockAccessibilityService extends MockAccessibilityService { + + /** + * The service {@link ComponentName} flattened as a string. + */ + static String sComponentName; + + /** + * Handle to the service instance. + */ + static MySecondMockAccessibilityService sInstance; + + /** + * Creates a new instance. + */ + public MySecondMockAccessibilityService() { + sInstance = this; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java new file mode 100644 index 0000000..38fed22 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2010 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.server; + +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reportMatcher; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; + +import org.easymock.IArgumentMatcher; + +import android.content.pm.ServiceInfo; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; +import android.view.accessibility.IAccessibilityManagerClient; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for the AccessibilityManager which mocking the backing service. + */ +public class AccessibilityManagerTest extends AndroidTestCase { + + /** + * Timeout required for pending Binder calls or event processing to + * complete. + */ + public static final long TIMEOUT_BINDER_CALL = 50; + + /** + * The reusable mock {@link IAccessibilityManager}. + */ + private final IAccessibilityManager mMockServiceInterface = + createStrictMock(IAccessibilityManager.class); + + @Override + public void setUp() throws Exception { + reset(mMockServiceInterface); + } + + @MediumTest + public void testGetAccessibilityServiceList() throws Exception { + // create a list of installed accessibility services the mock service returns + List<ServiceInfo> expectedServices = new ArrayList<ServiceInfo>(); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.name = "TestServiceInfoName"; + expectedServices.add(serviceInfo); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.getAccessibilityServiceList()).andReturn(expectedServices); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + List<ServiceInfo> receivedServices = manager.getAccessibilityServiceList(); + + // check expected result (list equals() compares it contents as well) + assertEquals("All expected services must be returned", receivedServices, expectedServices); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testInterrupt() throws Exception { + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + mockServiceInterface.interrupt(); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + manager.interrupt(); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @LargeTest + public void testIsEnabled() throws Exception { + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + replay(mockServiceInterface); + + // invoke the method under test + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + boolean isEnabledServiceEnabled = manager.isEnabled(); + + // check expected result + assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled); + + // disable accessibility + manager.getClient().setEnabled(false); + + // wait for the asynchronous IBinder call to complete + Thread.sleep(TIMEOUT_BINDER_CALL); + + // invoke the method under test + boolean isEnabledServcieDisabled = manager.isEnabled(); + + // check expected result + assertFalse("Must be disabled since the mock service is disabled", + isEnabledServcieDisabled); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception { + // create an event to be dispatched + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) + .andReturn(true); + expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) + .andReturn(false); + replay(mockServiceInterface); + + // invoke the method under test (manager and service in different processes) + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + manager.sendAccessibilityEvent(sentEvent); + + // check expected result + AccessibilityEvent nextEventDifferentProcesses = AccessibilityEvent.obtain(); + assertSame("The manager and the service are in different processes, so the event must be " + + "recycled", sentEvent, nextEventDifferentProcesses); + + // invoke the method under test (manager and service in the same process) + manager.sendAccessibilityEvent(sentEvent); + + // check expected result + AccessibilityEvent nextEventSameProcess = AccessibilityEvent.obtain(); + assertNotSame("The manager and the service are in the same process, so the event must not" + + "be recycled", sentEvent, nextEventSameProcess); + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + @MediumTest + public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception { + // create an event to be dispatched + AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); + + // configure the mock service behavior + IAccessibilityManager mockServiceInterface = mMockServiceInterface; + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(false); + replay(mockServiceInterface); + + // invoke the method under test (accessibility disabled) + AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface); + try { + manager.sendAccessibilityEvent(sentEvent); + fail("No accessibility events are sent if accessibility is disabled"); + } catch (IllegalStateException ise) { + // check expected result + assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage()); + } + + // verify the mock service was properly called + verify(mockServiceInterface); + } + + /** + * Determines if an {@link AccessibilityEvent} passed as a method argument + * matches expectations. + * + * @param matched The event to check. + * @return True if expectations are matched. + */ + private static AccessibilityEvent eqAccessibilityEvent(AccessibilityEvent matched) { + reportMatcher(new AccessibilityEventMather(matched)); + return null; + } + + /** + * Determines if an {@link IAccessibilityManagerClient} passed as a method argument + * matches expectations which in this case are that any instance is accepted. + * + * @return <code>null</code>. + */ + private static IAccessibilityManagerClient anyIAccessibilityManagerClient() { + reportMatcher(new AnyIAccessibilityManagerClientMather()); + return null; + } + + /** + * Matcher for {@link AccessibilityEvent}s. + */ + private static class AccessibilityEventMather implements IArgumentMatcher { + private AccessibilityEvent mExpectedEvent; + + public AccessibilityEventMather(AccessibilityEvent expectedEvent) { + mExpectedEvent = expectedEvent; + } + + public boolean matches(Object matched) { + if (!(matched instanceof AccessibilityEvent)) { + return false; + } + AccessibilityEvent receivedEvent = (AccessibilityEvent) matched; + return mExpectedEvent.getEventType() == receivedEvent.getEventType(); + } + + public void appendTo(StringBuffer buffer) { + buffer.append("sendAccessibilityEvent()"); + buffer.append(" with event type \""); + buffer.append(mExpectedEvent.getEventType()); + buffer.append("\""); + } + } + + /** + * Matcher for {@link IAccessibilityManagerClient}s. + */ + private static class AnyIAccessibilityManagerClientMather implements IArgumentMatcher { + public boolean matches(Object matched) { + if (!(matched instanceof IAccessibilityManagerClient)) { + return false; + } + return true; + } + + public void appendTo(StringBuffer buffer) { + buffer.append("addClient() with any IAccessibilityManagerClient"); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java new file mode 100644 index 0000000..17a1585 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2010 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.server; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.location.ICountryListener; +import android.os.RemoteException; +import android.test.AndroidTestCase; + +public class CountryDetectorServiceTest extends AndroidTestCase { + private class CountryListenerTester extends ICountryListener.Stub { + private Country mCountry; + + @Override + public void onCountryDetected(Country country) throws RemoteException { + mCountry = country; + } + + public Country getCountry() { + return mCountry; + } + + public boolean isNotified() { + return mCountry != null; + } + } + + private class CountryDetectorServiceTester extends CountryDetectorService { + + private CountryListener mListener; + + public CountryDetectorServiceTester(Context context) { + super(context); + } + + @Override + public void notifyReceivers(Country country) { + super.notifyReceivers(country); + } + + @Override + protected void setCountryListener(final CountryListener listener) { + mListener = listener; + } + + public boolean isListenerSet() { + return mListener != null; + } + } + + public void testAddRemoveListener() throws RemoteException { + CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext()); + serviceTester.systemReady(); + waitForSystemReady(serviceTester); + CountryListenerTester listenerTester = new CountryListenerTester(); + serviceTester.addCountryListener(listenerTester); + assertTrue(serviceTester.isListenerSet()); + serviceTester.removeCountryListener(listenerTester); + assertFalse(serviceTester.isListenerSet()); + } + + public void testNotifyListeners() throws RemoteException { + CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext()); + CountryListenerTester listenerTesterA = new CountryListenerTester(); + CountryListenerTester listenerTesterB = new CountryListenerTester(); + Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK); + serviceTester.systemReady(); + waitForSystemReady(serviceTester); + serviceTester.addCountryListener(listenerTesterA); + serviceTester.addCountryListener(listenerTesterB); + serviceTester.notifyReceivers(country); + assertTrue(serviceTester.isListenerSet()); + assertTrue(listenerTesterA.isNotified()); + assertTrue(listenerTesterB.isNotified()); + serviceTester.removeCountryListener(listenerTesterA); + serviceTester.removeCountryListener(listenerTesterB); + assertFalse(serviceTester.isListenerSet()); + } + + private void waitForSystemReady(CountryDetectorService service) { + int count = 5; + while (count-- > 0) { + try { + Thread.sleep(500); + } catch (Exception e) { + } + if (service.isSystemReady()) { + return; + } + } + throw new RuntimeException("Wait System Ready timeout"); + } +} diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java new file mode 100644 index 0000000..1bc9b86 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2010 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.server; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.Intent; +import android.os.Message; +import android.view.accessibility.AccessibilityEvent; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import junit.framework.TestCase; + +/** + * This is the base class for mock {@link AccessibilityService}s. + */ +public abstract class MockAccessibilityService extends AccessibilityService { + + /** + * The event this service expects to receive. + */ + private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>(); + + /** + * Interruption call this service expects to receive. + */ + private boolean mExpectedInterrupt; + + /** + * Flag if the mock is currently replaying. + */ + private boolean mReplaying; + + /** + * Flag if the system is bound as a client to this service. + */ + private boolean mIsSystemBoundAsClient; + + /** + * Creates an {@link AccessibilityServiceInfo} populated with default + * values. + * + * @return The default info. + */ + public static AccessibilityServiceInfo createDefaultInfo() { + AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo(); + defaultInfo.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED; + defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE; + defaultInfo.flags = 0; + defaultInfo.notificationTimeout = 0; + defaultInfo.packageNames = new String[] { + "foo.bar.baz" + }; + + return defaultInfo; + } + + /** + * Starts replaying the mock. + */ + public void replay() { + mReplaying = true; + } + + /** + * Verifies if all expected service methods have been called. + */ + public void verify() { + if (!mReplaying) { + throw new IllegalStateException("Did you forget to call replay()"); + } + + if (mExpectedInterrupt) { + throw new IllegalStateException("Expected call to #interrupt() not received"); + } + if (!mExpectedEvents.isEmpty()) { + throw new IllegalStateException("Expected a call to onAccessibilityEvent() for " + + "events \"" + mExpectedEvents + "\" not received"); + } + } + + /** + * Resets this instance so it can be reused. + */ + public void reset() { + mExpectedEvents.clear(); + mExpectedInterrupt = false; + mReplaying = false; + } + + /** + * Sets an expected call to + * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as + * argument. + * + * @param expectedEvent The expected event argument. + */ + public void expectEvent(AccessibilityEvent expectedEvent) { + mExpectedEvents.add(expectedEvent); + } + + /** + * Sets an expected call of {@link #onInterrupt()}. + */ + public void expectInterrupt() { + mExpectedInterrupt = true; + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent receivedEvent) { + if (!mReplaying) { + return; + } + + if (mExpectedEvents.isEmpty()) { + throw new IllegalStateException("Unexpected event: " + receivedEvent); + } + + AccessibilityEvent expectedEvent = mExpectedEvents.poll(); + assertEqualsAccessiblityEvent(expectedEvent, receivedEvent); + } + + @Override + public void onInterrupt() { + if (!mReplaying) { + return; + } + + if (!mExpectedInterrupt) { + throw new IllegalStateException("Unexpected call to onInterrupt()"); + } + + mExpectedInterrupt = false; + } + + @Override + protected void onServiceConnected() { + mIsSystemBoundAsClient = true; + } + + @Override + public boolean onUnbind(Intent intent) { + mIsSystemBoundAsClient = false; + return false; + } + + /** + * Returns if the system is bound as client to this service. + * + * @return True if the system is bound, false otherwise. + */ + public boolean isSystemBoundAsClient() { + return mIsSystemBoundAsClient; + } + + /** + * Compares all properties of the <code>expectedEvent</code> and the + * <code>receviedEvent</code> to verify that the received event is the one + * that is expected. + */ + private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(), + receivedEvent.getAddedCount()); + TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(), + receivedEvent.getBeforeText()); + TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(), + receivedEvent.isChecked()); + TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(), + receivedEvent.getClassName()); + TestCase.assertEquals("contentDescription has incorrect value", expectedEvent + .getContentDescription(), receivedEvent.getContentDescription()); + TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent + .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex()); + TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(), + receivedEvent.isEnabled()); + TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(), + receivedEvent.getEventType()); + TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(), + receivedEvent.getFromIndex()); + TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(), + receivedEvent.isFullScreen()); + TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(), + receivedEvent.getItemCount()); + assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent); + TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(), + receivedEvent.isPassword()); + TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(), + receivedEvent.getRemovedCount()); + assertEqualsText(expectedEvent, receivedEvent); + } + + /** + * Compares the {@link android.os.Parcelable} data of the + * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that + * the received event is the one that is expected. + */ + private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + String message = "parcelableData has incorrect value"; + Message expectedMessage = (Message) expectedEvent.getParcelableData(); + Message receivedMessage = (Message) receivedEvent.getParcelableData(); + + if (expectedMessage == null) { + if (receivedMessage == null) { + return; + } + } + + TestCase.assertNotNull(message, receivedMessage); + + // we do a very simple sanity check since we do not test Message + TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what); + } + + /** + * Compares the text of the <code>expectedEvent</code> and + * <code>receivedEvent</code> by comparing the string representation of the + * corresponding {@link CharSequence}s. + */ + private void assertEqualsText(AccessibilityEvent expectedEvent, + AccessibilityEvent receivedEvent) { + String message = "text has incorrect value"; + List<CharSequence> expectedText = expectedEvent.getText(); + List<CharSequence> receivedText = receivedEvent.getText(); + + TestCase.assertEquals(message, expectedText.size(), receivedText.size()); + + Iterator<CharSequence> expectedTextIterator = expectedText.iterator(); + Iterator<CharSequence> receivedTextIterator = receivedText.iterator(); + + for (int i = 0; i < expectedText.size(); i++) { + // compare the string representation + TestCase.assertEquals(message, expectedTextIterator.next().toString(), + receivedTextIterator.next().toString()); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java new file mode 100644 index 0000000..98966c0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2010 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.server.location; + +import android.location.Country; +import android.location.CountryListener; +import android.test.AndroidTestCase; + +public class ComprehensiveCountryDetectorTest extends AndroidTestCase { + private class TestCountryDetector extends ComprehensiveCountryDetector { + public final static String COUNTRY_ISO = "us"; + private boolean mLocationBasedDetectorStarted; + private boolean mLocationBasedDetectorStopped; + protected boolean mNotified; + private boolean listenerAdded = false; + + private Country mNotifiedCountry; + public TestCountryDetector() { + super(getContext()); + } + + public void notifyLocationBasedListener(Country country) { + mNotified = true; + mNotifiedCountry = country; + mLocationBasedCountryDetector.notifyListener(country); + } + + public boolean locationBasedDetectorStarted() { + return mLocationBasedCountryDetector != null && mLocationBasedDetectorStarted; + } + + public boolean locationBasedDetectorStopped() { + return mLocationBasedCountryDetector == null && mLocationBasedDetectorStopped; + } + + public boolean locationRefreshStarted() { + return mLocationRefreshTimer != null; + } + + public boolean locationRefreshCancelled() { + return mLocationRefreshTimer == null; + } + + @Override + protected CountryDetectorBase createLocationBasedCountryDetector() { + return new CountryDetectorBase(mContext) { + @Override + public Country detectCountry() { + mLocationBasedDetectorStarted = true; + return null; + } + + @Override + public void stop() { + mLocationBasedDetectorStopped = true; + } + }; + } + + @Override + protected Country getNetworkBasedCountry() { + return null; + } + + @Override + protected Country getLastKnownLocationBasedCountry() { + return mNotifiedCountry; + } + + @Override + protected Country getSimBasedCountry() { + return null; + } + + @Override + protected Country getLocaleCountry() { + return null; + } + + @Override + protected void runAfterDetectionAsync(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + runAfterDetection(country, detectedCountry, notifyChange, startLocationBasedDetection); + }; + + @Override + protected boolean isAirplaneModeOff() { + return true; + } + + @Override + protected synchronized void addPhoneStateListener() { + listenerAdded = true; + } + + @Override + protected synchronized void removePhoneStateListener() { + listenerAdded = false; + } + + @Override + protected boolean isGeoCoderImplemented() { + return true; + } + + public boolean isPhoneStateListenerAdded() { + return listenerAdded; + } + } + + private class CountryListenerImpl implements CountryListener { + private boolean mNotified; + private Country mCountry; + + public void onCountryDetected(Country country) { + mNotified = true; + mCountry = country; + } + + public boolean notified() { + return mNotified; + } + + public Country getCountry() { + return mCountry; + } + } + + public void testDetectNetworkBasedCountry() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_NETWORK); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getNetworkBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertFalse(listener.notified()); + assertFalse(countryDetector.locationBasedDetectorStarted()); + assertFalse(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + } + + public void testDetectLocationBasedCountry() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM); + final Country locationBasedCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCATION); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getSimBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.notifyLocationBasedListener(locationBasedCountry); + assertTrue(listener.notified()); + assertTrue(sameCountry(listener.getCountry(), locationBasedCountry)); + assertTrue(countryDetector.locationBasedDetectorStopped()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testLocaleBasedCountry() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCALE); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getLocaleCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertFalse(listener.notified()); + assertTrue(countryDetector.locationBasedDetectorStarted()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testStoppingDetector() { + // Test stopping detector when LocationBasedCountryDetector was started + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getSimBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.stop(); + // The LocationBasedDetector should be stopped. + assertTrue(countryDetector.locationBasedDetectorStopped()); + // The location refresh should not running. + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testLocationBasedCountryNotFound() { + final Country resultCountry = new Country( + TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM); + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected Country getSimBasedCountry() { + return resultCountry; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, resultCountry)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.notifyLocationBasedListener(null); + assertFalse(listener.notified()); + assertTrue(sameCountry(listener.getCountry(), null)); + assertTrue(countryDetector.locationBasedDetectorStopped()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testNoCountryFound() { + TestCountryDetector countryDetector = new TestCountryDetector(); + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + Country country = countryDetector.detectCountry(); + assertTrue(sameCountry(country, null)); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.notifyLocationBasedListener(null); + assertFalse(listener.notified()); + assertTrue(sameCountry(listener.getCountry(), null)); + assertTrue(countryDetector.locationBasedDetectorStopped()); + assertTrue(countryDetector.locationRefreshStarted()); + countryDetector.stop(); + assertTrue(countryDetector.locationRefreshCancelled()); + } + + public void testAddRemoveListener() { + TestCountryDetector countryDetector = new TestCountryDetector(); + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + assertTrue(countryDetector.isPhoneStateListenerAdded()); + assertTrue(countryDetector.locationBasedDetectorStarted()); + countryDetector.setCountryListener(null); + assertFalse(countryDetector.isPhoneStateListenerAdded()); + assertTrue(countryDetector.locationBasedDetectorStopped()); + } + + public void testGeocoderNotImplemented() { + TestCountryDetector countryDetector = new TestCountryDetector() { + @Override + protected boolean isGeoCoderImplemented() { + return false; + } + }; + CountryListenerImpl listener = new CountryListenerImpl(); + countryDetector.setCountryListener(listener); + assertTrue(countryDetector.isPhoneStateListenerAdded()); + assertFalse(countryDetector.locationBasedDetectorStarted()); + countryDetector.setCountryListener(null); + assertFalse(countryDetector.isPhoneStateListenerAdded()); + } + + private boolean sameCountry(Country country1, Country country2) { + return country1 == null && country2 == null || country1 != null && country2 != null && + country1.getCountryIso().equalsIgnoreCase(country2.getCountryIso()) && + country1.getSource() == country2.getSource(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java new file mode 100755 index 0000000..71e8e2a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2010 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.server.location; + +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; + +import android.location.Country; +import android.location.CountryListener; +import android.location.Location; +import android.location.LocationListener; +import android.test.AndroidTestCase; + +public class LocationBasedCountryDetectorTest extends AndroidTestCase { + private class TestCountryDetector extends LocationBasedCountryDetector { + public static final int TOTAL_PROVIDERS = 2; + protected Object countryFoundLocker = new Object(); + protected boolean notifyCountry = false; + private final Location mLocation; + private final String mCountry; + private final long mQueryLocationTimeout; + private List<LocationListener> mListeners; + + public TestCountryDetector(String country, String provider) { + this(country, provider, 1000 * 60 * 5); + } + + public TestCountryDetector(String country, String provider, long queryLocationTimeout) { + super(getContext()); + mCountry = country; + mLocation = new Location(provider); + mQueryLocationTimeout = queryLocationTimeout; + mListeners = new ArrayList<LocationListener>(); + } + + @Override + protected String getCountryFromLocation(Location location) { + synchronized (countryFoundLocker) { + if (!notifyCountry) { + try { + countryFoundLocker.wait(); + } catch (InterruptedException e) { + } + } + } + if (mLocation.getProvider().endsWith(location.getProvider())) { + return mCountry; + } else { + return null; + } + } + + @Override + protected Location getLastKnownLocation() { + return mLocation; + } + + @Override + protected void registerEnabledProviders(List<LocationListener> listeners) { + mListeners.addAll(listeners); + } + + @Override + protected void unregisterProviders(List<LocationListener> listeners) { + for (LocationListener listener : mLocationListeners) { + assertTrue(mListeners.remove(listener)); + } + } + + @Override + protected long getQueryLocationTimeout() { + return mQueryLocationTimeout; + } + + @Override + protected int getTotalEnabledProviders() { + return TOTAL_PROVIDERS; + } + + public void notifyLocationFound() { + // Listener could be removed in the notification. + LocationListener[] listeners = new LocationListener[mListeners.size()]; + mLocationListeners.toArray(listeners); + for (LocationListener listener :listeners) { + listener.onLocationChanged(mLocation); + } + } + + public int getListenersCount() { + return mListeners.size(); + } + + public void notifyCountryFound() { + synchronized (countryFoundLocker) { + notifyCountry = true; + countryFoundLocker.notify(); + } + } + + public Timer getTimer() { + return mTimer; + } + + public Thread getQueryThread() { + return mQueryThread; + } + } + + private class CountryListenerImpl implements CountryListener { + private boolean mNotified; + private String mCountryCode; + public void onCountryDetected(Country country) { + mNotified = true; + if (country != null) { + mCountryCode = country.getCountryIso(); + } + } + + public boolean notified() { + return mNotified; + } + + public String getCountry() { + return mCountryCode; + } + } + + public void testFindingCountry() { + final String country = "us"; + final String provider = "Good"; + CountryListenerImpl countryListener = new CountryListenerImpl(); + TestCountryDetector detector = new TestCountryDetector(country, provider); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.notifyLocationFound(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + assertNull(detector.getTimer()); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + assertTrue(countryListener.notified()); + assertEquals(countryListener.getCountry(), country); + } + + public void testFindingCountryCancelled() { + final String country = "us"; + final String provider = "Good"; + CountryListenerImpl countryListener = new CountryListenerImpl(); + TestCountryDetector detector = new TestCountryDetector(country, provider); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.notifyLocationFound(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + // The time should be stopped + assertNull(detector.getTimer()); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.stop(); + // There is no way to stop the thread, let's test it could be stopped, after get country + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + assertTrue(countryListener.notified()); + assertEquals(countryListener.getCountry(), country); + } + + public void testFindingLocationCancelled() { + final String country = "us"; + final String provider = "Good"; + CountryListenerImpl countryListener = new CountryListenerImpl(); + TestCountryDetector detector = new TestCountryDetector(country, provider); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.stop(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + // The time should be stopped + assertNull(detector.getTimer()); + // QueryThread should still be NULL + assertNull(detector.getQueryThread()); + assertFalse(countryListener.notified()); + } + + public void testFindingLocationFailed() { + final String country = "us"; + final String provider = "Good"; + long timeout = 1000; + TestCountryDetector detector = new TestCountryDetector(country, provider, timeout) { + @Override + protected Location getLastKnownLocation() { + return null; + } + }; + CountryListenerImpl countryListener = new CountryListenerImpl(); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + waitForTimerReset(detector); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + // QueryThread should still be NULL + assertNull(detector.getQueryThread()); + assertTrue(countryListener.notified()); + assertNull(countryListener.getCountry()); + } + + public void testFindingCountryFailed() { + final String country = "us"; + final String provider = "Good"; + TestCountryDetector detector = new TestCountryDetector(country, provider) { + @Override + protected String getCountryFromLocation(Location location) { + synchronized (countryFoundLocker) { + if (! notifyCountry) { + try { + countryFoundLocker.wait(); + } catch (InterruptedException e) { + } + } + } + // We didn't find country. + return null; + } + }; + CountryListenerImpl countryListener = new CountryListenerImpl(); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + detector.notifyLocationFound(); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + assertNull(detector.getTimer()); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + // CountryListener should be notified + assertTrue(countryListener.notified()); + assertNull(countryListener.getCountry()); + } + + public void testFindingCountryWithLastKnownLocation() { + final String country = "us"; + final String provider = "Good"; + long timeout = 1000; + TestCountryDetector detector = new TestCountryDetector(country, provider, timeout); + CountryListenerImpl countryListener = new CountryListenerImpl(); + detector.setCountryListener(countryListener); + detector.detectCountry(); + assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + waitForTimerReset(detector); + // All listeners should be unregistered + assertEquals(detector.getListenersCount(), 0); + Thread queryThread = waitForQueryThreadLaunched(detector); + detector.notifyCountryFound(); + // Wait for query thread ending + waitForThreadEnding(queryThread); + // QueryThread should be set to NULL + assertNull(detector.getQueryThread()); + // CountryListener should be notified + assertTrue(countryListener.notified()); + assertEquals(countryListener.getCountry(), country); + } + + private void waitForTimerReset(TestCountryDetector detector) { + int count = 5; + long interval = 1000; + try { + while (count-- > 0 && detector.getTimer() != null) { + Thread.sleep(interval); + } + } catch (InterruptedException e) { + } + Timer timer = detector.getTimer(); + assertTrue(timer == null); + } + + private void waitForThreadEnding(Thread thread) { + try { + thread.join(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private Thread waitForQueryThreadLaunched(TestCountryDetector detector) { + int count = 5; + long interval = 1000; + try { + while (count-- > 0 && detector.getQueryThread() == null) { + Thread.sleep(interval); + } + } catch (InterruptedException e) { + } + Thread thread = detector.getQueryThread(); + assertTrue(thread != null); + return thread; + } +} |