-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate GpsLocationEntity from atmosphere-client
- Loading branch information
Showing
6 changed files
with
424 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
src/main/java/com/musala/atmosphere/agent/entity/EntityTypeResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package com.musala.atmosphere.agent.entity; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.util.Set; | ||
|
||
import org.reflections.Reflections; | ||
|
||
import com.musala.atmosphere.agent.entity.annotations.Restriction; | ||
import com.musala.atmosphere.commons.DeviceInformation; | ||
|
||
/** | ||
* Class responsible for resolving the correct implementation of the entities, defined for all device specific | ||
* operations, depending on the provided {@link DeviceInformation}. | ||
* | ||
* @author filareta.yordanova | ||
* | ||
*/ | ||
public class EntityTypeResolver { | ||
private static final String ENTITIES_PACKAGE = "com.musala.atmosphere.agent.entity"; | ||
|
||
private DeviceInformation deviceInformation; | ||
|
||
private Reflections reflections; | ||
|
||
public EntityTypeResolver(DeviceInformation information) { | ||
this.deviceInformation = information; | ||
reflections = new Reflections(ENTITIES_PACKAGE); | ||
} | ||
|
||
/** | ||
* Finds entity implementation for a device specific operation depending on the {@link DeviceInformation device | ||
* information} and the hierarchy type given. | ||
* | ||
* @param baseEntityClass | ||
* - base class of the entity hierarchy for a device specific operation | ||
* @return {@link Class} of the entity that matches the required {@link DeviceInformation device information} and is | ||
* from type baseEntityClass | ||
*/ | ||
@SuppressWarnings({"unchecked", "rawtypes"}) | ||
public Class<?> getEntityClass(Class baseEntityClass) { | ||
Set<Class<?>> subClasses = reflections.getSubTypesOf(baseEntityClass); | ||
Class<?> defaultImplementation = null; | ||
|
||
for (Class<?> subClass : subClasses) { | ||
Annotation annotation = subClass.getAnnotation(Restriction.class); | ||
if (annotation == null) { | ||
defaultImplementation = subClass; | ||
} else if (isApplicable((Restriction) annotation)) { | ||
return subClass; | ||
} | ||
} | ||
|
||
return defaultImplementation; | ||
|
||
} | ||
|
||
/** | ||
* Checks if a certain implementation annotated with {@link @Restriction} is applicable for a device with the | ||
* provided {@link DeviceInformation information}. | ||
* | ||
* @param restriction | ||
* - restrictions provided for a certain entity implementation | ||
* @return <code>true</code> if the given restrictions are compatible with the {@link DeviceInformation information} | ||
* for the current device, <code>false</code> otherwise | ||
*/ | ||
// TODO: Check for default values in the annotation methods, if the parameter has default value and is not present | ||
// in the annotation it is not considered when checking for applicability. | ||
private boolean isApplicable(Restriction restriction) { | ||
String manufacturer = restriction.manufacturer(); | ||
|
||
if (!manufacturer.equals(DeviceInformation.FALLBACK_MANUFACTURER_NAME) | ||
&& !manufacturer.equalsIgnoreCase(deviceInformation.getManufacturer())) { | ||
return false; | ||
} | ||
|
||
boolean isApplicable = true; | ||
int[] apiLevels = restriction.apiLevel(); | ||
|
||
if (apiLevels.length > 0) { | ||
int deviceApiLevel = deviceInformation.getApiLevel(); | ||
isApplicable = false; | ||
|
||
for (int applicableApiLevel : apiLevels) { | ||
if (applicableApiLevel == deviceApiLevel) { | ||
isApplicable = true; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return isApplicable; | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/main/java/com/musala/atmosphere/agent/entity/GpsLocationCheckBoxEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.musala.atmosphere.agent.entity; | ||
|
||
import java.util.List; | ||
|
||
import com.musala.atmosphere.agent.devicewrapper.util.ondevicecomponent.ServiceCommunicator; | ||
import com.musala.atmosphere.agent.devicewrapper.util.ondevicecomponent.UIAutomatorCommunicator; | ||
import com.musala.atmosphere.agent.entity.annotations.Restriction; | ||
import com.musala.atmosphere.commons.exceptions.CommandFailedException; | ||
import com.musala.atmosphere.commons.exceptions.UiElementFetchingException; | ||
import com.musala.atmosphere.commons.ui.UiElementPropertiesContainer; | ||
import com.musala.atmosphere.commons.ui.selector.CssAttribute; | ||
import com.musala.atmosphere.commons.ui.selector.UiElementSelector; | ||
import com.musala.atmosphere.commons.ui.tree.AccessibilityElement; | ||
|
||
/** | ||
* {@link GpsLocationEntity} responsible for setting the GPS location state on all Samsung devices. | ||
* | ||
* @author yavor.stankov | ||
* | ||
*/ | ||
@Restriction(apiLevel = {17, 18}) | ||
public class GpsLocationCheckBoxEntity extends GpsLocationEntity { | ||
private static final String ANDROID_WIDGET_CHECK_BOX_CLASS_NAME = "android.widget.CheckBox"; | ||
|
||
GpsLocationCheckBoxEntity(ServiceCommunicator serviceCommunicator, | ||
UIAutomatorCommunicator automatorCommunicator, | ||
HardwareButtonEntity hardwareButtonEntity, | ||
GestureEntity gestureEntity) { | ||
super(serviceCommunicator, automatorCommunicator, hardwareButtonEntity, gestureEntity); | ||
} | ||
|
||
@Override | ||
protected UiElementPropertiesContainer getChangeStateWidget() throws UiElementFetchingException, CommandFailedException { | ||
UiElementSelector checkBoxWidgetSelector = new UiElementSelector(); | ||
checkBoxWidgetSelector.addSelectionAttribute(CssAttribute.CLASS_NAME, ANDROID_WIDGET_CHECK_BOX_CLASS_NAME); | ||
|
||
automatorCommunicator.waitForExists(checkBoxWidgetSelector, CHANGE_STATE_WIDGET_TIMEOUT); | ||
|
||
List<AccessibilityElement> widgetList = automatorCommunicator.getUiElements(checkBoxWidgetSelector, true); | ||
|
||
if (!widgetList.isEmpty()) { | ||
// There are more than one check box on the screen, but only the first one is for setting the GPS location | ||
// state. | ||
return widgetList.get(0); | ||
} | ||
|
||
return null; | ||
} | ||
} |
165 changes: 165 additions & 0 deletions
165
src/main/java/com/musala/atmosphere/agent/entity/GpsLocationEntity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package com.musala.atmosphere.agent.entity; | ||
|
||
import java.util.List; | ||
|
||
import org.apache.log4j.Logger; | ||
|
||
import com.musala.atmosphere.agent.devicewrapper.util.ondevicecomponent.ServiceCommunicator; | ||
import com.musala.atmosphere.agent.devicewrapper.util.ondevicecomponent.UIAutomatorCommunicator; | ||
import com.musala.atmosphere.commons.exceptions.CommandFailedException; | ||
import com.musala.atmosphere.commons.exceptions.UiElementFetchingException; | ||
import com.musala.atmosphere.commons.geometry.Bounds; | ||
import com.musala.atmosphere.commons.geometry.Point; | ||
import com.musala.atmosphere.commons.ui.UiElementPropertiesContainer; | ||
import com.musala.atmosphere.commons.ui.selector.CssAttribute; | ||
import com.musala.atmosphere.commons.ui.selector.UiElementSelector; | ||
import com.musala.atmosphere.commons.ui.tree.AccessibilityElement; | ||
|
||
/** | ||
* Base entity responsible for handling GPS location state changing. | ||
* | ||
* @author yavor.stankov | ||
* | ||
*/ | ||
public abstract class GpsLocationEntity { | ||
private static final Logger LOGGER = Logger.getLogger(GpsLocationEntity.class.getCanonicalName()); | ||
|
||
private static final String AGREE_BUTTON_RESOURCE_ID = "android:id/button1"; | ||
|
||
private static final int AGREE_BUTTON_TIMEOUT = 3000; | ||
|
||
protected static final int CHANGE_STATE_WIDGET_TIMEOUT = 5000; | ||
|
||
private static final int UI_ELEMENT_OPERATION_WAIT_TIME = 500; | ||
|
||
protected ServiceCommunicator serviceCommunicator; | ||
|
||
protected UIAutomatorCommunicator automatorCommunicator; | ||
|
||
protected HardwareButtonEntity hardwareButtonEntity; | ||
|
||
protected GestureEntity gestureEntity; | ||
|
||
GpsLocationEntity(ServiceCommunicator serviceCommunicator, | ||
UIAutomatorCommunicator automatorCommunicator, | ||
HardwareButtonEntity hardwareButtonEntity, | ||
GestureEntity gestureEntity) { | ||
this.serviceCommunicator = serviceCommunicator; | ||
this.automatorCommunicator = automatorCommunicator; | ||
this.hardwareButtonEntity = hardwareButtonEntity; | ||
this.gestureEntity = gestureEntity; | ||
} | ||
|
||
/** | ||
* Gets the right widget that is responsible for setting the GPS location state. | ||
* | ||
* @return the widget that should be used for setting the GPS location state. | ||
* @throws UiElementFetchingException | ||
* if the required widget is not present on the screen | ||
* @throws MultipleElementsFoundException | ||
* if there are more than one widgets present on the screen | ||
*/ | ||
protected abstract UiElementPropertiesContainer getChangeStateWidget() | ||
throws UiElementFetchingException, CommandFailedException; | ||
|
||
/** | ||
* Enables the GPS location on this device. | ||
* | ||
* @return <code>true</code> if the GPS location enabling is successful, <code>false</code> if it fails | ||
* @throws CommandFailedException | ||
*/ | ||
public boolean enableGpsLocation() throws CommandFailedException { | ||
return setGpsLocationState(true); | ||
} | ||
|
||
/** | ||
* Disables the GPS location on this device. | ||
* | ||
* @return <code>true</code> if the GPS location disabling is successful, <code>false</code> if it fails | ||
* @throws CommandFailedException | ||
*/ | ||
public boolean disableGpsLocation() throws CommandFailedException { | ||
return setGpsLocationState(false); | ||
} | ||
|
||
/** | ||
* Check if the GPS location is enabled on this device. | ||
* | ||
* @return <code>true</code> if the GPS location is enabled, <code>false</code> if it's disabled | ||
* @throws CommandFailedException | ||
*/ | ||
public boolean isGpsLocationEnabled() throws CommandFailedException { | ||
return serviceCommunicator.isGpsLocationEnabled(); | ||
} | ||
|
||
private boolean setGpsLocationState(boolean state) throws CommandFailedException { | ||
if (isGpsLocationEnabled() == state) { | ||
return true; | ||
} | ||
|
||
openLocationSettings(); | ||
|
||
try { | ||
UiElementPropertiesContainer changeStateWidget = getChangeStateWidget(); | ||
if (tap(changeStateWidget)) { | ||
pressAgreeButton(); | ||
} | ||
} catch (UiElementFetchingException e) { | ||
LOGGER.error("Failed to get the wanted widget, or there are more than one widgets on the screen that are matching the given selector.", | ||
e); | ||
return false; | ||
} | ||
|
||
// TODO: If needed, move the HardwareButton enumeration from com.musala.atmosphere.client.device to atmosphere-commons, | ||
// so the enumeration could be used here instead of an integer. | ||
hardwareButtonEntity.pressButton(4); // Back button | ||
|
||
return true; | ||
} | ||
|
||
public void openLocationSettings() throws CommandFailedException { | ||
serviceCommunicator.openLocationSettings(); | ||
} | ||
|
||
private void pressAgreeButton() throws UiElementFetchingException, CommandFailedException { | ||
UiElementSelector agreeButtonSelector = new UiElementSelector(); | ||
agreeButtonSelector.addSelectionAttribute(CssAttribute.RESOURCE_ID, AGREE_BUTTON_RESOURCE_ID); | ||
|
||
if (automatorCommunicator.waitForExists(agreeButtonSelector, AGREE_BUTTON_TIMEOUT)) { | ||
List<AccessibilityElement> elementsList = automatorCommunicator.getUiElements(agreeButtonSelector, true); | ||
|
||
AccessibilityElement agreeButton = elementsList.get(0); | ||
tap(agreeButton); | ||
} | ||
} | ||
|
||
private boolean tap(UiElementPropertiesContainer element) { | ||
Bounds elementBounds = element.getBounds(); | ||
Point centerPoint = elementBounds.getCenter(); | ||
Point point = elementBounds.getRelativePoint(centerPoint); | ||
Point tapPoint = elementBounds.getUpperLeftCorner(); | ||
tapPoint.addVector(point); | ||
|
||
boolean tapSuccessful = false; | ||
if (elementBounds.contains(tapPoint)) { | ||
tapSuccessful = gestureEntity.tapScreenLocation(tapPoint); | ||
finalizeUiElementOperation(); | ||
} else { | ||
String message = String.format("Point %s not in element bounds.", point.toString()); | ||
LOGGER.error(message); | ||
throw new IllegalArgumentException(message); | ||
} | ||
|
||
return tapSuccessful; | ||
} | ||
|
||
private void finalizeUiElementOperation() { | ||
// Should be invoked exactly once in the end of all element-operating | ||
// methods, whether its directly or indirectly invoked. | ||
try { | ||
Thread.sleep(UI_ELEMENT_OPERATION_WAIT_TIME); | ||
} catch (InterruptedException e) { | ||
LOGGER.info(e); | ||
} | ||
} | ||
} |
Oops, something went wrong.