From 31cf06b62abb05e45d1083f26bfbd473022c232f Mon Sep 17 00:00:00 2001 From: John Richardson <john.richardson@arm.com> Date: Wed, 4 May 2016 11:10:07 +0100 Subject: [PATCH] Extended BaseUiAutomation and UxPerfUiAutomation Moved common functionality to base classes for code reuse. Refactored googlephotos, reader and gmail workloads to take advantage of the new base class methods and removed total test time metric. New methods for BaseUiAutomation: uiDeviceVertPinchIn, uiDeviceVertPinchOut New methods for UxPerfUiAutomation: uiObjectVertPinchTest, writeResultsToFile, startDumpsysSurfaceFlinger, startDumpsysSurfaceFlinger, startDumpsysGfxInfo, stopDumpsysGfxInfo New class for UxPerfUiAutomation: GestureTestParams --- .../arm/wlauto/uiauto/BaseUiAutomation.java | 48 +++++- .../arm/wlauto/uiauto/UxPerfUiAutomation.java | 87 +++++++++++ .../com/arm/wlauto/uiauto/UiAutomation.java | 31 +--- wlauto/workloads/googlephotos/__init__.py | 7 +- .../com.arm.wlauto.uiauto.googlephotos.jar | Bin 11019 -> 11215 bytes wlauto/workloads/googlephotos/uiauto/build.sh | 4 +- .../googlephotos/uiauto/project.properties | 2 +- .../com/arm/wlauto/uiauto/UiAutomation.java | 139 ++---------------- .../com/arm/wlauto/uiauto/UiAutomation.java | 106 ++----------- 9 files changed, 167 insertions(+), 257 deletions(-) diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java index c31a835c..22266ca6 100644 --- a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java @@ -22,13 +22,13 @@ import java.io.InputStreamReader; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; -import android.app.Activity; import android.os.Bundle; +import android.graphics.Point; +import android.graphics.Rect; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; import com.android.uiautomator.testrunner.UiAutomatorTestCase; @@ -207,4 +207,48 @@ public class BaseUiAutomation extends UiAutomatorTestCase { getDisplayCentreHeight(), steps); } + + public void uiDeviceVertPinchIn(UiObject view, int steps, int percent) throws Exception { + final int FINGER_TOUCH_HALF_WIDTH = 20; + + // Make value between 1 and 100 + percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent; + float percentage = percent / 100f; + + Rect rect = view.getVisibleBounds(); + if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) + throw new IllegalStateException("Object width is too small for operation"); + + // Start at the top-center and bottom-center of the control + Point startPoint1 = new Point(rect.centerX(), rect.centerY() + (int) ((rect.height() / 2) * percentage)); + Point startPoint2 = new Point(rect.centerX(), rect.centerY() - (int) ((rect.height() / 2) * percentage)); + + // End at the same point at the center of the control + Point endPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH); + Point endPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH); + + view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); + } + + public void uiDeviceVertPinchOut(UiObject view, int steps, int percent) throws Exception { + final int FINGER_TOUCH_HALF_WIDTH = 20; + + // Make value between 1 and 100 + percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent; + float percentage = percent / 100f; + + Rect rect = view.getVisibleBounds(); + if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) + throw new IllegalStateException("Object width is too small for operation"); + + // Start from the same point at the center of the control + Point startPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH); + Point startPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH); + + // End at the top-center and bottom-center of the control + Point endPoint1 = new Point(rect.centerX(), rect.centerY() + (int) ((rect.height() / 2) * percentage)); + Point endPoint2 = new Point(rect.centerX(), rect.centerY() - (int) ((rect.height() / 2) * percentage)); + + view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); + } } diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java index 756d0ea0..379908c3 100644 --- a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java @@ -17,6 +17,7 @@ package com.arm.wlauto.uiauto; import android.os.Build; import android.os.SystemClock; +import android.os.Bundle; import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; @@ -26,6 +27,7 @@ import com.android.uiautomator.core.UiSelector; import com.arm.wlauto.uiauto.BaseUiAutomation; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.InputStreamReader; @@ -34,6 +36,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.Arrays; import java.util.List; +import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; public class UxPerfUiAutomation extends BaseUiAutomation { @@ -209,4 +215,85 @@ public class UxPerfUiAutomation extends BaseUiAutomation { results.end(); return results; } + + public Timer uiObjectVertPinchTest(UiObject view, PinchType direction, + int steps, int percent) throws Exception { + Timer results = new Timer(); + results.start(); + if (direction.equals(PinchType.IN)) { + uiDeviceVertPinchIn(view, steps, percent); + } else if (direction.equals(PinchType.OUT)) { + uiDeviceVertPinchOut(view, steps, percent); + } + results.end(); + return results; + } + + public class GestureTestParams { + public GestureType gestureType; + public Direction gestureDirection; + public PinchType pinchType; + public int percent; + public int steps; + + public GestureTestParams(GestureType gesture, Direction direction, int steps) { + this.gestureType = gesture; + this.gestureDirection = direction; + this.pinchType = PinchType.NULL; + this.steps = steps; + this.percent = 0; + } + + public GestureTestParams(GestureType gesture, PinchType pinchType, int steps, int percent) { + this.gestureType = gesture; + this.gestureDirection = Direction.NULL; + this.pinchType = pinchType; + this.steps = steps; + this.percent = percent; + } + } + + public void writeResultsToFile(LinkedHashMap timingResults, String file) throws Exception { + // Write out the key/value pairs to the instrumentation log file + FileWriter fstream = new FileWriter(file); + BufferedWriter out = new BufferedWriter(fstream); + Iterator<Entry<String, Timer>> it = timingResults.entrySet().iterator(); + + while (it.hasNext()) { + Map.Entry<String, Timer> pairs = it.next(); + Timer results = pairs.getValue(); + long start = results.getStart(); + long finish = results.getFinish(); + long duration = results.getDuration(); + out.write(String.format(pairs .getKey() + " " + start + " " + finish + " " + duration + "\n")); + } + out.close(); + } + + public void startDumpsysSurfaceFlinger(Bundle parameters, String view) { + if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { + initDumpsysSurfaceFlinger(parameters.getString("package"), view); + } + } + + public void stopDumpsysSurfaceFlinger(Bundle parameters, String view, + String filename) throws Exception { + if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { + File out_file = new File(parameters.getString("output_dir"), filename); + exitDumpsysSurfaceFlinger(parameters.getString("package"), view, out_file); + } + } + + public void startDumpsysGfxInfo(Bundle parameters) { + if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { + initDumpsysGfxInfo(parameters.getString("package")); + } + } + + public void stopDumpsysGfxInfo(Bundle parameters, String filename) throws Exception { + if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { + File out_file = new File(parameters.getString("output_dir"), filename); + exitDumpsysGfxInfo(parameters.getString("package"), out_file); + } + } } diff --git a/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java index b2ab4dfe..58a0b516 100644 --- a/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ b/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -1,24 +1,16 @@ package com.arm.wlauto.uiauto.gmail; import android.os.Bundle; -import android.os.SystemClock; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; -import com.android.uiautomator.testrunner.UiAutomatorTestCase; import com.arm.wlauto.uiauto.UxPerfUiAutomation; -import java.io.BufferedWriter; -import java.io.FileWriter; import java.util.concurrent.TimeUnit; -import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; public class UiAutomation extends UxPerfUiAutomation { @@ -55,7 +47,7 @@ public class UiAutomation extends UxPerfUiAutomation { .className("android.widget.ListView")); if (!converationView.waitForExists(networkTimeout)) { throw new UiObjectNotFoundException("Could not find \"converationView\"."); - }; + } } public void clickNewMail() throws Exception { @@ -112,9 +104,9 @@ public class UiAutomation extends UxPerfUiAutomation { UiObject attachIcon = getUiObjectByResourceId("com.google.android.gm:id/add_attachment", "android.widget.TextView"); - String [] imageFiles = {"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"}; + String[] imageFiles = {"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"}; - for ( int i=0; i < imageFiles.length; i++) { + for ( int i = 0; i < imageFiles.length; i++) { result.start(); clickUiObject(attachIcon, timeout); @@ -174,21 +166,4 @@ public class UiAutomation extends UxPerfUiAutomation { timingResults.put(String.format("AttachFiles" + "_" + file), result); } } - - private void writeResultsToFile(LinkedHashMap timingResults, String file) throws Exception { - // Write out the key/value pairs to the instrumentation log file - FileWriter fstream = new FileWriter(file); - BufferedWriter out = new BufferedWriter(fstream); - Iterator<Entry<String, Timer>> it = timingResults.entrySet().iterator(); - - while (it.hasNext()) { - Map.Entry<String, Timer> pairs = it.next(); - Timer results = pairs.getValue(); - long start = results.getStart(); - long finish = results.getFinish(); - long duration = results.getDuration(); - out.write(String.format(pairs.getKey() + " " + start + " " + finish + " " + duration + "\n")); - } - out.close(); - } } diff --git a/wlauto/workloads/googlephotos/__init__.py b/wlauto/workloads/googlephotos/__init__.py index db0107b3..f041defe 100644 --- a/wlauto/workloads/googlephotos/__init__.py +++ b/wlauto/workloads/googlephotos/__init__.py @@ -39,8 +39,6 @@ class Googlephotos(AndroidUiAutoBenchmark): def __init__(self, device, **kwargs): super(Googlephotos, self).__init__(device, **kwargs) self.output_file = os.path.join(self.device.working_directory, self.instrumentation_log) - self.camera_dir = self.device.path.join(self.device.external_storage_directory, - 'DCIM/Camera/') def validate(self): super(Googlephotos, self).validate() @@ -56,7 +54,7 @@ class Googlephotos(AndroidUiAutoBenchmark): wa_file = ''.join([self.file_prefix, entry]) if entry.endswith(".jpg"): self.device.push_file(os.path.join(self.dependencies_directory, entry), - os.path.join(self.camera_dir, wa_file), + os.path.join(self.device.working_directory, wa_file), timeout=300) # Force a re-index of the mediaserver cache to pick up new files @@ -91,9 +89,8 @@ class Googlephotos(AndroidUiAutoBenchmark): context.output_directory) self.device.delete_file(os.path.join(self.device.working_directory, entry)) - for entry in self.device.listdir(self.camera_dir): if entry.startswith(self.file_prefix) and entry.endswith(".jpg"): - self.device.delete_file(os.path.join(self.camera_dir, entry)) + self.device.delete_file(os.path.join(self.device.working_directory, entry)) # Force a re-index of the mediaserver cache to removed cached files self.device.execute('am broadcast -a android.intent.action.MEDIA_MOUNTED -d file:///sdcard') diff --git a/wlauto/workloads/googlephotos/com.arm.wlauto.uiauto.googlephotos.jar b/wlauto/workloads/googlephotos/com.arm.wlauto.uiauto.googlephotos.jar index 2a4fdd3dfe20bdec746f079613637275d236be80..cf790ce49740a4abeb93a8a4001c0b1ac8abf7c2 100644 GIT binary patch delta 10968 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 G00014kDNyU delta 10770 zcmV+tD(%(JSBq8+P)h>@3IG5I005V0qmd0L1D0r`kvNGCmuRC%4h#LEUnu|pd{~js z6@T1SwfDLIWwvaSnQTqBX%c9<q)A)4k+e;-v`v$yX|~dYXPhR}bZ9y=nVB>RvXrtE z6a;181eGEPN`Zokh^VNj_>_uI-op*W4}2~U{SftoC*SYfduK99i-6zrOZolJ`Ja34 zcJ96Bo_lAQq0oe<X5}h!<sM$s^uFXhhkqAb^oK{@|JAaApRM`gn%TQgJMS!_7!ghM zp5Y_?_xW8!R|zHG4BX`)Y6pGXNmL0sltuJqmS+=zrQ4A6P!rLcexk=#5`77H5_k)! zKZB?RXajZvJ;3{cQD7W67dRg{0$dGT3)~ER9QZu&B=9uw3xHM;Ie>g%El>+|1AoIn z5*P<Q09*(h1}+1x0ImkE1#Sdx1?~in0`~zA0gnRT2YwCMtBFd1MqmWE8+aDTT7&9= zLEvWKS-`oLXaf)fZUdeLiq@gOzyaVY;27{d;5UGMCeeJL8fXU21}1?I0k;DW0nY$G z16~D;TB2D%9nc3%0#^Z_0G<GT0Drs;7<H%@r~)<tyMU*G?dxG1;4ttSU~eEQ2G#@J zzyrYCjYP|U9l!)|E${&F2=HCt1n>r6)Dz_Z1wbiK4lD)E0Gfanpa(b~xCFQkxC^)k z_#*He@H@b{2{r)M0=2+Kpb6Ltv;jMTUf^sX2n+)e;2<ywoDUoZJ_vjm_<tyHBX9@s zN#H*Ke*@7rU;s!0<G`iBjldnieZc2{r+^oLKLPoTM2mn1pdIK1-Umd0ao{rGYTzc| zQ@}&Omw|5pKL=h0eh&n&4t4_<0mp#n0cSJ%3v2>ffwO@9zyxp=a075Fa5wN6@HOB$ z;HSWgz;A&!02_-jA1DRp1AmKwRlo*dE6@S-13@4H90qO&?g2gvJO(@kJOexjya2om zybAmk$l5|w0+azYz*b-Y7zGXkR|B^Ip9Ve$d>QyQ@B`qdz;A#*1J+ieTwnpP6j%pz z0VBYLz!ku)z<t1R;7h>QfoFh!2Yv^*T8OfNB47@%6j%<d1Db#~pnnrM3m5{zz$kDo za3OF7a6Rw|;6C8<z!SjNfoFk#2VMqV1>WR(wqY#*6+jiR2511<fL*}*f&IXFz@@<T zz%9UC!2Q5Oz!!k81K$Py4fq-GD)2gBv=Vs$KM(*G1IvN6z;+-E90V=~J`7wB+zUJf zJP-T~_)p+ZfM`QmKz|WX4paf_ffk@27zSd%LEsYL!@zaGZNO3BIPe7UH1Iv(hrlbq z8$jN6j6bjv*bM9jV!*|~O~4(%J-{*GQQ%47yTHEzzXJXOSlBFbf!V-fU_H<VbOC1r zgTOd&E^q-*3fDdlSO8Q3%YbU223QWP0I-#i-U0~jnBaa1?thlxRtc_>;06gUkKpDA zZjIow2=0d94hYMeurvuvk(%|MhG|NeW`v<93=hGqgzgiXFZa3~KnKtXbOGH!56}x> zdn9a|v>WIH`hl~7e*xYH3;^%veG(W3!oUb{0EhrlQhVzMfg6C20e1j*0v`uH0en*1 zMCd(vzUI$fp?~aTXnC3DhafKpwX~dhM+WcI{35``?a}%kEk8~3Nyry!{=5wS67Wkk zf2Ee^YB}?H8N65Xi$OW4xDNah&EL5~#SqNjuiFsNZCI$=dVGayC%-?gb(UtdpZTY> zyecDaRYn_l4n3oFsx$0Rlc8Uu^%rUWxfRL|%e5U&*MIzwE_1oo=W<qMl*4oJ$6CHF zL%v??^EhwN@(*dA%fC|dFM+>G^IX@5HUC@iS8M*Y4EcE({F~rEqInC8<T}lJ!C$ZW zIV-31muB#1Wbkzvd{YL0Nk$vmAiqZQ-QYi}`Li>4o>$kV`jR1!Wt7R|l9ErXoYFs( zp>yR*wSP7?YP;R2dA8e4nrFM+toiGpe~aeXhPP|}F34}wJlpVA&9fa+{P7I=@eFxt ztT$!MpN0&+QS&xd<|Jq~R^nkS(j0gmUX!`VX-S$(#hO-VI;!corWX^hp$4g+hks+5 zUZm+^9#hn4N$QnrhUuuL3DA7hdP37zG;I}3{eQGq@S0QUOn<Cthm@;yKW6MKNevnX zEkHj^=oO-Uzn3(R{!3CpO9WFdtq|&cr=&%)oFc5I^NFd0D&_lX+9#<&=Lz=LXVSSm z=kV_Xpe8&5`x5TkqndtB(;T7bWrF?LTrvgwvIhDhDWCS5rhk&uL4TFBo>oh}M(UAt z3xEBKV86Fi*5g9@7d3rcQkQhDF4W|fw3(_TZJ-)WmrJ^ddNtjp=~<fYmb8g3(DY(Z zH@pPb=Yc0t)I*<_G>g8V>31Z}rspMfQ?5|*jhgP!bWqYcbdKONmnts{o@<-nd8gi) zJ|-#AA2t2on!cv#pCq;Eza@2$CG$JU)_>HYsZ-L0y8La@e%TnO_v!iubov2JBbr7v z9hGzqT`1{dIwa{5O;^!HlCIWtGhHIr&FKKIhZJ2c`^A_qnC8%gq^&e5cwH#HJgt|f z_44pGA!(b`^J+b>*7IW5|EZ*TQqPNBe^RcKWip-D&Fh-}SyLg`NtI0Zp}c?B>wo8U zP5-Q^(mP$QJHH+mURN(lx|V(+>6!FPN$V(2>a8NLr0XbOQe{`awyR&;)sK>6`h8T= ze2Pno^)J_}>SrN#yj)3ZG!=A@ruWKqsq~6;`<Q-D(;rHz+F7LA$?`u)s^(3Ro;O8$ z-W2J1Q>5ojk?v2C?hog0lyo6&)qixGmbYpB?V5J#d|g^EBB|<kk?v;^`kj=tmaYJu zgXoa^F$d*6A?X~dg?ug|MwZXj^0_Gga=~%cIyI-Tqbs^f(*k->Ql-BDktfS5F#i_| zjtAz@o5Xv&nnw#^QJz1QI)5cJ)(DO#)caCx-zv~&NW~{FN$RJ+==bvl$A2mtrQR|` z!rzdzM9!aTnngb+rnBiS;uuAhTZ72;o086>UlYeAs-ER&`Gb-MG+m&n@*68KKfgsx z=hEMZ<LPpGh&Yz1r)<Hrh-M3puT^=g(es~3T1;mOj<<{G|B#|C!Ld#m`K3QMDJ(b! zSM6JmDC!MKH&Bt_{j-R^M1Q<@D%z+i`y)Z>4v9#`gwG)m`;Q*=&U2K5$yDrj1=|!s z*oI#9F4t<kinUVdbx3d3>3OF}??(E$I^BDU^bw?gT&L&j_GQ+83DTeLooesQ^y`rR zvQGEw^z4lEyO5r{OT8D&=-(y4kC9%l(+jmcGyS(n->%b(bou^_^nW*zp491__N#71 zf8VXn34aFtTQ|vloq0EGUD%^^Dte~mRp8fUyl(~H-7{6jU<M!2`6o5KT+^Gi&S!g6 zohkkio&Kbz-|A5@6xVf8yBgP+_jK&WdoUA=42vQ)pQnJCMSb-GsU~!}tRwB}JTgjm zNh_C%5)lb}5+==MPk$`SzN%ft2NCa5ynE;cG=t|wD*f7Ydete@Z*EuP7x7l(eH1%1 zzel{B$AcM`(!1kr^juocg;sO_j<&0_Mj0e!!h?R!fqo<~pMqF}k-$9U%@(mh4tmiA zXBerOg`RuFI#CRLH<cQN<SQ><*+f2TyTi161>2pbH5&L7R)2N25ev-5nqMgL)AfDf zZR#ugC)M}ex2dn<f4Dvimgo9@p~vq;%`4_b6s=iEhT!?*mg$22vt6A9Bi?1u%NC{P z5wyaFPs}!WtROSXta(&sFGpMN!90uv?xRa7+b(r@Bp(7F@!pS=Y&%x-VXCkrfo%9N zkC|uYkk4QF-+w#@=kq)eOO{yL4$d9Qug#bbH4gNa+h^+e?%R=?H%riB)aYHzR77f4 z$)33I#^?B1JJi{f%X4JRuO)bcHgbU)nwl?|rg3bAPBpdEdcm>`F_v_2yHp&k%3k;m zWlL(jU>L@+1yeOGN!OGr|5RnK`nzSnAcQfqL``LDkAF4(y|NqMl})(aGcBR1{eo>9 z$GS7i&XqHZXy*=f$1_Tw!86ODeT=KrT`YafO_Xhw*>kAOwE#7K47sx1vB0&E#{!pQ z{^)$<Gj4N{W4X(fUF*Wku8ag$(g%^_gFK(iwWb?$>~4CPJg&8_*WdE_)*7Wqy$$81 z@}21_LVqo_RO-HveD3m_n}}D#t+Ku^puUB!R9%(dVc)3M#Yc9i`yW4@WkhP$W7Xv1 zUCqs#cc}ZA6CJ!}cm-Nmsp}98l);8&LahXWwRovq0SkELdcE97Sn@Q~0GgKH4^3Xt zv$3KVlA7D94WF9UhK;ffPThtFkX9o7gz^#KgntXQl~V^wFEt+^_7!T)=O8Edp}>&~ ztEv3o*rD#RPSmSDl;UK_e%6%xo=xA|q3-lX<!xU!N+?H^6GqGSFt{#n6MKB&^R>c> zE2%XlEt`_sQ_>YvQs<PkVM^+ll6JC`&n%UH1O5QK1pEm2Hp#h~g|q@mJ@S4p3)WdI zsedK!m$JAgV(})ixKS)_7K^ut#Vt-!d-4x=sQXIaX5T7bh0pIZ%3$46aWTbe8p&&f z+5bvOv67f!l&T~ZN@9g!CnXtI5<83*D9NajIKmh~Uq?A&IJOz0bc3w~YO-BGEw%)x z&3y+&4pZ*YYMlOKhdRH<YHmQQ)V|E?>wm=^$_8`zE+lXx=1w+jkeiOV7_VrZJt!&S zeO!*?e6)mhY;A|%?NIwo$_9C8`$ed2tF%EDDam0afeo^?WK2n50}uIX$`PCH5m~x_ z*}8uo*}pKK%G3S%<J<J-uj&5eW%MU+T7URLS@j32-uorppN47uu{);s$BVWGP=DKY z*`Hiel7y0=KVB^vRT8u|Pxhw~{mIq+@#_BM>Hg$o^vBb2>i*<+r26B{=#O_=e@fE* z$w7af(EVwYIv&g$)|uO(;^_Y70<7~}QCHt)KlN?(QCB_C;HSp}ef9a2+7tP2X@{~! zS0G=;=qdT~wA?pMzB(=UOSvU`fq(Nz2kG-I>Mt*m^&8X|C<clEzSpvK8O>TZ<xBhO zi_k)#p;)%CP{*D8*OsoYa9TTdrsYM`<h^Nm@icjVhl*X&bs{~Nrefh>hx%+J=B-4Z z=D?3eyw^)Bk9yTTPk;TaQ^d%nD1|+tg);Y{3|=`t%ZFWLncP8|u}9sBoqr<|_&7a} zGCu)dk?kzA=R<xq{R*BK`<h$rYlx3LyszCW_qC76oeTcnvDR@q@_z);QK`F7jCwzd znUL*f$@NtE`yA8hb#`}u3$61kq|&S-6si9r-CMiXVqf}Ev}`R$THY(<jvMjbLF`R$ z$J66p>#joWl~m?Y`;Z6uXMf3k=rOcKYh^_O_v-ii`Th)Tya{bw=+1F-&8Mnop?j^Z zq&BS;E2LBqYgc|b2m9L1m>ZR^r8p^nI4!^T6x<IoICteYQFC|YQ#oekrRaTk<zZRE zM|mw3;5=SL>RyZQe|@NzKeKY<9NsMFLyr6m%EU_H&z?@#_sJ$sfPWWAI!m7IOtkz8 zN!{eRuyRdOR9-Da<#oWVz@5NR;M0O05EoV+7yNuwJP1Fg*7_|w)n{nF)xOhw1wQlr ze0O9N(HRS7lW$XbelvFb@6&cen59OR0JXNegk5U<hGmXX?*lyYm}h5`)16D@!Bgv} z^eTVrCQH`)$WHYcoqx|NGt`(Gay<9QojyzQD|Ac|modiuGR7!xRIv`yu*a5{p*|D! znTF-_ms;o1cFQn*<)xNOaVxhA!{pix-Adb~;%5u(Ik8iHo>qp_fX^&nsn$S^Tmvz= z21wR?2lTOLi|#?pT?=#9zD&;D#==UzI+bGw8~Z)bljq-^>VI<{-<FfDI-h(yPia;6 zlvPWO4J>n}ts59Xn_=D2x63E>)1y)O3dyq<x5eA}&$YArRCY#tZ5>;38<upc&u_~3 z&Mwfy7WVRIq<e%RBO?nDc@v`kCD_$Ca_6g(e3Y`6x~TH6k-#3Ne~JVKnf@UX7-IUn zGEoU@#EDlqpMU>&15%#bN$Q-%XLh~^DV8!@&bw`$>T{Y@TLS5}z~=<ou`WGgEzX>g z8b1x%skZQbIhFeQTalUqq*nfc%T;HCp?Bz`{Wg6pp&{AFLZ;|r5mWTBnCW}$ql$A) zy0lY$E;%Zr-KjWtHb#B6jA<g?mT7S=OS7k>Wm8hmlz()^$+2rHW_9YA)zUF5S|JuM z5sRzD;%bifRhvK9soL7_TOjvcLH+)@)L$?M?aHHm?|j-_7=ZnDd*{hL&#B{u+dI|0 z+Vp&TklK$^LVtnEx4USbzT@G)hdPx#I=ul(-QDotV`=$3DOXaWuXd_<tCS9ku#9)7 z_S|oFs(*X8n72QD|5T8%x1I+5)kx)g)BgGjQg;n1@4t4c?-Tf)h$)MFXG8)Q&{it7 zz6vjLAIg}Ztu|Yp+fr(M4blfvQj?`7q?c>yBufpJ@*2K?_ThOkO}qnNYCVPg_oqq~ zoL}hAo>Uv(=v3cdD0TdZ%1kwXua)!HB%d=DIDa2IPpR`ORO(WtOuD!gZ0k~I0i>Ui zZG%*>6lX-|laPKkrE7EFd1j9xC0~~^&MCzDsMk3G=>w@!ESB=A>mbJu&WP6|BY|GD zru)VUXdS0vd0#Rcb;<jLE}};<kfnvMQdz-jjNn6<kEK?hD6<P`56<y>oHqGg9(cu4 zihrGT4%r7?R3J;Lw@YW8A<8V)^FYrFy&>|uSCGrbTyX41OIX(>W=VaoRXS^BhJNll z>s$R9?Qp(*JFK%a^jvSNmlO|!&$i;;&++Ly+gBmAFiQ0l_a4J~_gHJK(?#6-Ao%Q@ z`8jJLe-!IB+nSGdch}D)7ebF|vTbvwzklzTKka?gJHlStH(hpsWw~XR+E=*Wg>pYZ ztE^J{F3EROwjK9AqI1<qSu^n%-!Juic6pE5X`q=uRn{K6ER{dAEbQy~8Fd^@%THNe zR%Y8W^WFXT^ZCn4YA}Dkj9fS&1dhV8WyLr<Tk2lIB41%75YX+xSvtU<?(-;gS%0Sy zOZfhz-u77QkfRKHWT{<5d>hhD3$26VLTIwKOK|Cj_`K%zLE?p^RIWZR;*8mp6fKAm zJ$932*-I?mT~fJRh99T1!#JI(vA>9p$+T{(LiW_f`xR!3B^J@2|HdhX@Ii73-6^uY zl|NDC6q8{<T7M~d7ejg?QyP2UWq;(YhV+|QX+oV&I9B4%Mfkf{4jt?Kq)Ww*@^05q z_JLn8{qo)>bypM69**yGGPS-gZ=G4I%v4gG>H`a~zr*_l=4RgYNY65L&;NRtItO*5 zmQwL7@%^n=o`SBEo^35=oJy+@skBJoTp7uw?uq~QkCajK0WwTJBdRi7-G9m^-OHh8 zs51BtjLQ%*S}28W=IQh@kxJ)VW3Q#86=`XtCXO94SK8N<XYkVhm-6kC%CAq5-%_9d z+R}F}L;3vqZ&{v?4jMh!6WQ<Qkw3eDYO%BW3)tQTbXXKRo^H4|M>vuqA2TdFH{!L> zQjhREDrt4lkFi~Z9VCg_cz+885v%yckj%Np`{G;v0z@-EDYN*_M#x+dFOP)B@w~%P z5Uas?$WdmWGWEBdTIeV<PEL(@`Bur}e8K574v#<mqa)+T4zjk-QD(jy?FXf{-?2yD zsrzJv=an|i!8*t%!{Mi-gQI)ieex(jkK@4Uny<a(a^#b9i}Nh!B7f(ZMwZIqpvsr| zV?1@nIszLXWE)$2n!bi!qcX=_a37-0DEVQuD%;K8+%0|OF^&~+;ys(#gWK2v=R1`L z$GO`z--X(}yU~g)oO91&ioJLjQ!2|=-Y%Qi<3ig#->p3EZQx6@h2+1*XY!g))4f?I z%NCg@%iMxxG$n(_<bQL$FT4C1<uRo+a24G^l^>6IuSD;2)3MWuZuMQeR9sxI<Kl>S zjQy_Y-&Bq^$H2$DN%<U=dq2m=-J2>fd-n_WWDDe5cLV0LSuW?Z^bIvfsKCgjsXw=- z{QW(vK;4@$U9Ra0O=~sf?}zgLqp)7n4VrG$G`oPN{gvqVRDaya-}kEIdS=MCYI*$( z`F1Vum{IQ8Gv4><cRQ61Ocbc^k;Xtny8XkN?$h+pjB>7=@qSZ*YD>yaeD`o?f%@(# z=O5KHsp)x|p0DXenjY5lGEI+Y%4HYo{u&xJ_?NmLWOy{K(Ufg1eJSP2cT4hK`6;UU z&Yb!@^)y}H>3_({bSW*B@-^vtn&f+veiu5QOTWAHyI<Gm*LxV(S0uk7B&5BW_iEkL zzQ;76X_=<w>3W5xhNkmm`8ASLI;(Y?*XXo5ysy;dH0yB|>3^xaU-d854)hiwdw|wg zbd86TgZs7zV-<tZ0~Nvefr{~w;8-$RF&37jXQCq%-+$W^-h`I}!DKiZsb~tvLxarG z#(yOHmQW%&77uk##zN$*=-t%T)7(Wkotl-4ddUFXRaGOwWGFH?NqK3uYH%bJjFYu2 z78xc{O`?umb%P_}NI1EH9CeBs8*0hgP>V7gn+69%u_P5X1QVgm>X)4pJQyTLWAH#I z&NMm_9e;~cNn><uWGE1cCIfrJk)c4@X^ArVpgA*BV*8@WXy9NtG+wrhtfn2i+Q~g- zk<*&v@n}3SzAroy3M7O3!;#^@(Aa@kVluHb5Sj=l8>2&^^&~b^@#dEHEzO++-8*_3 zw+?LG)V6tGS4&g(R?1Hq2&5wz4<1NRR;JAm%6}OxZ9qGTZDefQ#^2bt+L||alhKB> zHVE6=$ZW$49-PLk%}-A5qms5@WGEgD4^<DxgRy<#!9;aOG#p9RQt`<to#<IDm8KJ- ziRy;2@W@a_Z*yl?%Z~P1@=s5T42^_pX<-{CM0F}b%?6A>G+sRzjfbjx!cC!r;lX#K zxPN2MIkE|B-<@K6G`Ts7Ml?_G1i+Zo(xP|D*%cZ=&S?DaRg%OI#K$6$P`o-lpQ3Rd zq()4!TB=kz;|HqM3ecIWXHLLcTKYDcr&ue?Yw3)4ubrNTwY2)(=gOE{@2R$qaAa_w z%C+j<=jsk02=xX>#zKi&s(EkOFg%Ojb$^apTKP7*!_nyQNJuW@MD<K-H@@dgC#}|6 zTK--Osim^Eq2S2D@P14^xEd*}ZjKC&L=*5JjU&MXik)`~ofe*uI(@+@(zk~W?9mEr z@wul^=?V`=g4_jO<tHb1?~6yrk+r0aJ!f?oGkR?A-cUR=)ENp6@t~ZXvMU}=a(_y0 zI%RX1okGF1_moy)TG|qcjU~I1@lfzUEzLfaI8~=Nop`b&Rjyx2MuL&y>V{|(`$r_* zSSf7W7mRm>M#r$fpmF(`DaxXG8KUOM*nw0fX+gU2N;2hg7G#K9Mn*!z!I3T)G?bw- zCqtz@+BG(~Z*w>_G9)(;9;unB+kZpJebH31FC#hSAT#^h5swarxC^Bx3#I#>X`s%r zND^yoddaXkre7`1I!Tr;W19M8A{m-t6FGf?dq$>89!rKtsyD^s!O28=Mo3u;MlC44 zaP$<?x(pe|8_khqe6sH3#=cjdvH|~aCjFnU@x8bV*@%*i`r5*g{h^_)!G8qDRw?_R zoKj1%w@dnGH8iy-bd8B*YEh&GwNx=fa*DQWNKcdL_N}Ge|FnUok^i3~I?YgBA(&-; zhF~xn861npLy=@PH?IfrE5|}_s}LPaX84ZG2{95K9!8_9+d>CJBk2V{JqeT-tJU6v zc(ABF8W>823V}o{G#K6+4u1`ixud6>7AkKN7!MC6_XWa<Kr$K)Bn|{eMgn^g*hXW# zHaOC?J6hTsw~{*@29Vv^vSn*GSzViYo5|VL)H=}8-VLU?aYuVo7dg6`5i&MYZr50R zZ*VZQ8NoYRMP~P=E##Ss3+Qy_hXUwvU_2O3atO^;qZ5O{WFQ%uNPnu(+?L}?xjijS z&Aly+%>!M#S~{BfeMiH#=EiRQZuE3e{>evx;#9!Mos{7{M~!C&kPu;ig^mZ}p+qQ| zir=YZDglvwC=g7i6!j!Ruo(O^+8m4of`fx7EQz8{nvAM)fs;-z!t-8^$a@j6_ab2L z<$%2x;k$*$HfGzH#edW8l_9*NSH<w6kBmO(_JMcc(ZZ$lp_D$9(#MK@Z8C$=-$JYU z|DgbK&3ni;ErNV6^<{*R{{B<#X#J<yjKlzC4-D^}Xo>8NF2f8b??3`GN>3*#aRk-r z&%-p4sHmx_N%PCWXDNR3NN_kog*w__hABTBN-k>{OC~YDOMhmFTMh(=L%lfmQR$47 z&frius&f_1Nb3rP_BY_%T0BGEjae)U^8{nDj^N;a6pm)cVxh<oXhJ&H+&$AGEpqNr zyDsGn%H!)+h|y_s4#G=>;%B9p-B7>*bSQPKA@?B1GwsMkS@ImM))Lt`Ko9Px5=k~i zhIV1OZH~s<Lx1DDaMq2EvqCfyk|YtL8Vu|*<ph_d`hzAXmMNFGY+v+1XxUagHenJS z3@0aP&EL;)CeB!cvCsfcR-wpna^FA#<{H=od!7Ey8AC(iBvrk$9?Xpjg7G9=+`A|? z!d7}0#YQlK1AEk{SHH757S<$8A0ObG0}NF3zyWBI3x94t84O2o#@F7Sof_q5ylfAT zjD(Rs6iN)n!*T;4f5uB=ss!f{A5o)|WWu|XJMH4hHxy1B2qzM|LL;bGO=4%tcjakj zASAsTnlv<~UAzd9o2|^3B$#x}c5sJyn<E#$swvFefb1$cQ>B<Zk==O6n}y|5><ZQy zlD%|=cz^%J)<_OS;lYsvv{HK<S)qy0AZ78LNt14>FXLs4asV_dLpEb$adVQ~Q^?wj z^9FhLrcWK@VqT85V{bSTPV57X#t-nKkQ)J%v5%vp!6YoZHz7|S<Q!J6j=Y+gy7|c( z&bXs->6Zj$r#NY}B231na3VGmoNVOIhqi{o!+-mdR5(+<i??5{B_-C1c~cUllEs45 zfVuIqS@k9tY{t1s>a|4B9h`N@ftS|Mq|{fj0rxB^9R$l#*G|mFdCSLA_TWVy5@bT! zxw!a+*B9ldxX{p$_J?jMV#h5rrLKLbM7}6L-!R#fF|~`QrF4c8(XseosAWi&K9#@@ zRDV{j2AD0?8%~5V_ZoQk5-@<8A=nROznpjv^Nexs3ntn*w_~5OsSRHjnxHJcLrhP# zjF<FO%aF~OY6e`9E3D5AWI-CDTz)P~gknJ@HRaAUqCBr#(LmLp6mc1roQ)Ry74q;? zo6wBzIfook*>7@2<8b-G5lF{SB_0E6YJY>nU=Q%h&(ge1aK`lJlBF5BWUW{biZ(wJ z<lz<`2%+@}a>UfoXM-j}`qYd<LUG<&psE-yumn6WneOqZT-c$wjssAx^aXZ|w|8V_ z(h?z;WOkt4?pQi#RKkI9L<<MFU(Q%$Sa!u7i}QgMEvLK~W)V-X!TsHFm>x>%IDbpI zv1#X8GGlBS%=)1=9tTHU?Vc8|RI+7EN1pi9#DYR%Yy`C`isc?3i^w^X2PQLMAuAC) z2xBL(g`)j(5+y=#;X~w1=$$?*As2>pl;ldJR*9V$358-Ha;5ksnK8dgGlerISe8uF zzBJ=y1-a#m^rt916vNz2B%`sJbALBOHlt`?M)Z_gEOuf%jKOhB(i9z!Xtpi17q*jZ zr(8Qu$@j#xaA1TLvJ@Ye8ZPxR7NcB_n!5($p-?0N>yRfIjA>s+-YH%=K%`FHe><7) z4i1yW(LZJLGrjG(_-%j}iIv=kjncu-iW+26$0o`FgWh%O(RC%GdP@bTeSedkj3%+D ztRzp#{4wpNy3)O$E|OO8O%o|=Ojna0KW~}{hJudrVJi4#owR5wTVYDn3+J1unZVW{ z&l#Ka8N)v|!3%L9^KeS`LFpBplI++^j)UrGWy$En#ZMItGN+H1<Q!MwALVGKc>+;r zg7WpN3L53C_pnGckX+15X@8dFQdZ}e9_$w*TR4QwPrtTDb;g`=6_02?XUiieS`m%! z=e2K-^G;1ZN%Y`#NBQEA!~_`=H6$jfoSeDqX(ygH(8mM_-6EeKmCqaG^8({L;xXaO zJw9R59ftdeD9op%@uV?RcDXQqd6KYFeA95|hH0s|6|~h9pEjKPO@HH2W4Q=;=gc$d zIkA-P7RHCnt4wF^QThZ|d7K^=;so?3T93CTHqqny?RaarXA^y0zlB?m<L%Qa&2Z}z zo2bY5hVhE5$nmUU9yP5m7|VWX+Fvr}wwm-UVa_v+--!Bfo3KptdSiK2xNL=Ot`8gM z{+GiH+9oxKk0Ey-ZGSdCW^luAI3WtWv_||=N^8+UmiC$SZ6oW*geiVyxa(-UR;HV! zy_S%3H!U$p^0M13%B7{`tfi$&uFS!rT&iMum6BH}c@^_3%$JR4j9<f`eN|Nxw25Ww z`LT{4)#7EtxfCh#H9SF^S=zvl4g6TeQaYW-_iHq6&?~~q`G2NZ8+PW-Gp(0JV7^Jm z#oQyN>oKFcuGMtHP<^&#+DK}>+a^;W6;^VvmFn)Z9m24MgXY<`?X+E{`E#+zVcl4@ zzG{PQ8l!V<vMq;j*baxo>2TGBPYAC+l}8Ab$zef#^K?x}gq2cqSf!~{Axz^rv1q$( z2;nfMs~EOJ7Jp}QlGfM)Y3BQ85CcWoTnRXt5t(KRQD(Y5;M29xpz)x1ML4&GMWN3k zHe4MVC%g`#PV+(WhJnPnVs4%(?#5Iu$<sRg%Mg}R+4+b`j|jG{0o!gwJx1v~Q^0Vv zTRbS7yTf8uX3+&Gy4B#KGYj^n3cgVo-;*;PlkcmBzkkl~JzQr*=WfTJ5x<PSEvz@B z!u?Y!M>f{YIzq>U`NrSPdM-~FOb`0K&}vmxtyOjODs1z6@vx!Ggbb7IMIo-lLQT_I zoVgf}`lGZG6S=Lm&7_MB_j*wCG7}X2f8B6zsFQyz7Ps@FxrW!uGSl%3V|kxxe#P)_ zXuaGtet#s~>yMb?hai`m_S1&DA6C3a3aziBl*zh2>O#h9vyd}d1uq(F_KJc$i~#=! zroelJm3^(TdhncZ*vk2ov2Nt3S@b)@d&KnotH?WI<{dNgkD9&{hH<x<d%t0b)AP*y zuff!nd1ls!K<DI{{vR64Z!}BZ#9O}CEPlgqe}C<Yy2%?&FRW+eK72#JG5YM0`A6p; zU1)pkEIY@}wexJRop1YWzg=V(+a<<<x%8kASHo~`ne-?+aKnu)lcsopyMB{_9=!Mn zeaPU!X6|DKPqy3_Pnh%xWBwyH(MJqJ&aq6{SEi3~9$K@$+RUC~v`(5ipD{$Mnfruc zAAd1Ea2Yr0c1MmQ_f^ySo?-pbw7+jyqBzeqUJ#&O)BLfpUaVhloI6+e^5_j={Mz_m z>}X8ucOuYdIzDd9yWFy2#yzm2^B!Z}{;=sneXXYF8N<Yo{#xKkKM<y9@bc8=#btZy zC_r+WVLa>3!Eo12o*-kk__Hxbl(<t&rGHTjro?ZQ7@rmuF7GYIJjKqczr`pnF-lp@ zD&AR~UA(q9PrZs_M~2SaOr3SmnMyjCnRKR3vIJ5tm|_nZv!qgquf$uDSCXspVBm`P zB3FO0R|%YHL0<9JVy9|$kt)VOF>_QgqNFfYz@XHwE6#%-DH5XcqC?g_ix!FJ7Jmux z7vQGFLVO<h`C?OCz63Jhm8C*FUj-g`bs2Q3g?JLUtp+mSx#dC}TY<EdSaN3w@fvW= zDj~kVN`5Qnrog7l4_U9Sa`6Aob4x(nd`Qeh0~Ef$MjSq5{bjA;zGj_pA75wjuPIGe z-vYJg*Rk5uXBzIGpDEmz)UsO6bbqxrsNGY`YB$vx?qhYr{ahVc!p!#x{&!7aCp!I? zx=wNbdLf<ye!L#Zb|D-x{#_I3LGo+sd&Qj_gm?yc33wB@ZX+4OJqzx;9~#Fuo-O{e zQHV>LkO~~y3@ZUIZL!2FTMhAA3vzE`3(USL5ayP*l7s&@Jcta5YXavSf`4J!1fKV7 z7ncWq%#YW$3o~1kxyuDNCxVPuwhJDmc452(T;5?CPw#XaPn~5LkM9=7p*~?e1>Dtd z8JC<bj91=wo_Oi~Uj3adkwH_x$;;m@O8qV`f4?a8+r0eUrqu8A@^^|-ztPLz{Y?E% zujy64d&1v$69M(xhxZgfCq1Ba`1e<YSN--K|JI-pQ1$Zf`kGpwf1|?+sNXN)-xndB zSN?8`SGARYL-_v!P)h*<6axwX00;m8muRDt5-T_ZmT04sO)EbRmuRC%4h#LEUnu|p Qd{~p3D=!8=DgXcg0394Nf&c&j diff --git a/wlauto/workloads/googlephotos/uiauto/build.sh b/wlauto/workloads/googlephotos/uiauto/build.sh index f60c0b43..dc6235ec 100755 --- a/wlauto/workloads/googlephotos/uiauto/build.sh +++ b/wlauto/workloads/googlephotos/uiauto/build.sh @@ -1,9 +1,9 @@ #!/bin/bash class_dir=bin/classes/com/arm/wlauto/uiauto -base_class=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.class')"` +base_classes=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.class')"` mkdir -p $class_dir -cp $base_class $class_dir +cp $base_classes $class_dir ant build diff --git a/wlauto/workloads/googlephotos/uiauto/project.properties b/wlauto/workloads/googlephotos/uiauto/project.properties index 916037e3..ce39f2d0 100644 --- a/wlauto/workloads/googlephotos/uiauto/project.properties +++ b/wlauto/workloads/googlephotos/uiauto/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-23 +target=android-18 diff --git a/wlauto/workloads/googlephotos/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/googlephotos/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java index 1c7a8c22..b9d9b81e 100644 --- a/wlauto/workloads/googlephotos/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ b/wlauto/workloads/googlephotos/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -1,8 +1,6 @@ package com.arm.wlauto.uiauto.googlephotos; import android.os.Bundle; -import android.graphics.Point; -import android.graphics.Rect; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; @@ -11,9 +9,6 @@ import com.android.uiautomator.core.UiSelector; import com.arm.wlauto.uiauto.UxPerfUiAutomation; -import java.io.File; -import java.io.FileWriter; -import java.io.BufferedWriter; import java.util.concurrent.TimeUnit; import java.util.LinkedHashMap; import java.util.Iterator; @@ -25,21 +20,16 @@ public class UiAutomation extends UxPerfUiAutomation { public static String TAG = "uxperf_googlephotos"; public Bundle parameters; - private long viewTimeout = TimeUnit.SECONDS.toMillis(20); + private long viewTimeout = TimeUnit.SECONDS.toMillis(10); private LinkedHashMap<String, Timer> timingResults = new LinkedHashMap<String, Timer>(); public void runUiAutomation() throws Exception { - Timer result = new Timer(); - result.start(); parameters = getParams(); dismissWelcomeView(); gesturesTest(); editPhotoTest(); - result.end(); - timingResults.put("total", result); - writeResultsToFile(timingResults, parameters.getString("output_file")); } @@ -50,10 +40,10 @@ public class UiAutomation extends UxPerfUiAutomation { sleep(3); // Pause while splash screen loads - UiObject getStarteddButton = + UiObject getStartedButton = getUiObjectByResourceId("com.google.android.apps.photos:id/get_started", "android.widget.Button"); - getStarteddButton.clickAndWaitForNewWindow(); + getStartedButton.clickAndWaitForNewWindow(); UiObject welcomeButton = getUiObjectByResourceId("com.google.android.apps.photos:id/name", @@ -79,7 +69,7 @@ public class UiAutomation extends UxPerfUiAutomation { nextButton.clickAndWaitForNewWindow(); } - private void gesturesTest () throws Exception { + private void gesturesTest() throws Exception { String testTag = "gestures"; // Perform a range of swipe tests while browsing photo gallery @@ -112,10 +102,10 @@ public class UiAutomation extends UxPerfUiAutomation { if (!view.waitForExists(viewTimeout)) { throw new UiObjectNotFoundException("Could not find \"photo view\"."); - }; + } - startDumpsysGfxInfo(); - startDumpsysSurfaceFlinger(viewName); + startDumpsysGfxInfo(parameters); + startDumpsysSurfaceFlinger(parameters, viewName); Timer results = new Timer(); @@ -133,8 +123,8 @@ public class UiAutomation extends UxPerfUiAutomation { break; } - stopDumpsysSurfaceFlinger(viewName, surfFlingerlogName); - stopDumpsysGfxInfo(gfxInfologName); + stopDumpsysSurfaceFlinger(parameters, viewName, surfFlingerlogName); + stopDumpsysGfxInfo(parameters, gfxInfologName); timingResults.put(runName, results); } @@ -172,9 +162,9 @@ public class UiAutomation extends UxPerfUiAutomation { timingResults.put(testTag, result); } - // Helper to click on an individual photographs based on index in Camera gallery. - private void selectPhoto(int index) throws Exception { - UiObject cameraHeading = new UiObject(new UiSelector().text("Camera")); + // Helper to click on an individual photographs based on index in wa-working gallery. + private void selectPhoto(final int index) throws Exception { + UiObject cameraHeading = new UiObject(new UiSelector().text("wa-working")); cameraHeading.clickAndWaitForNewWindow(); UiObject photo = @@ -183,109 +173,4 @@ public class UiAutomation extends UxPerfUiAutomation { .index(index))); photo.click(); } - - // Helper for testing zoom facility. NOTE: the built in UiObject methods - // pinchIn() and pinchOut() do not zoom appropriately for this application. - private Timer uiObjectVertPinchTest( - UiObject view, PinchType direction, - int steps, int percent) throws Exception { - - Timer results = new Timer(); - results.start(); - - final int FINGER_TOUCH_HALF_WIDTH = 20; - - // make value between 1 and 100 - percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent; - float percentage = percent / 100f; - - Rect rect = view.getVisibleBounds(); - if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) - throw new IllegalStateException("Object width is too small for operation"); - - // start from the same point at the center of the control - Point startPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH); - Point startPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH); - - // End at the top-center and bottom-center of the control - Point endPoint1 = new Point(rect.centerX(), rect.centerY() + (int) ((rect.height() / 2) * percentage)); - Point endPoint2 = new Point(rect.centerX(), rect.centerY() - (int) ((rect.height() / 2) * percentage)); - - if (direction.equals(PinchType.IN)) { - view.performTwoPointerGesture(endPoint1, endPoint2, startPoint1, startPoint2, steps); - } else if (direction.equals(PinchType.OUT)) { - view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); - } - - results.end(); - - return results; - } - - private class GestureTestParams { - GestureType gestureType; - Direction gestureDirection; - PinchType pinchType; - private int percent; - private int steps; - - GestureTestParams(GestureType gesture, Direction direction, int steps) { - this.gestureType = gesture; - this.gestureDirection = direction; - this.pinchType = PinchType.NULL; - this.steps = steps; - this.percent = 0; - } - - GestureTestParams(GestureType gesture, PinchType pinchType, int steps, int percent) { - this.gestureType = gesture; - this.gestureDirection = Direction.NULL; - this.pinchType = pinchType; - this.steps = steps; - this.percent = percent; - } - } - - private void writeResultsToFile(LinkedHashMap timingResults, String file) throws Exception { - // Write out the key/value pairs to the instrumentation log file - FileWriter fstream = new FileWriter(file); - BufferedWriter out = new BufferedWriter(fstream); - Iterator<Entry<String, Timer>> it = timingResults.entrySet().iterator(); - - while (it.hasNext()) { - Map.Entry<String, Timer> pairs = it.next(); - Timer results = pairs.getValue(); - long start = results.getStart(); - long finish = results.getFinish(); - long duration = results.getDuration(); - out.write(String.format(pairs .getKey() + " " + start + " " + finish + " " + duration + "\n")); - } - out.close(); - } - - private void startDumpsysSurfaceFlinger(String view) { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - initDumpsysSurfaceFlinger(parameters.getString("package"), view); - } - } - - private void stopDumpsysSurfaceFlinger(String view, String filename) throws Exception { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - File out_file = new File(parameters.getString("output_dir"), filename); - exitDumpsysSurfaceFlinger(parameters.getString("package"), view, out_file); - } - } - - private void startDumpsysGfxInfo() { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - initDumpsysGfxInfo(parameters.getString("package")); - } - } - - private void stopDumpsysGfxInfo(String filename) throws Exception { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - File out_file = new File(parameters.getString("output_dir"), filename); - exitDumpsysGfxInfo(parameters.getString("package"), out_file); - } - } } diff --git a/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java index 41a3ceac..f2334102 100755 --- a/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ b/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -7,16 +7,10 @@ import android.os.SystemClock; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; -import com.android.uiautomator.core.UiCollection; -import com.android.uiautomator.testrunner.UiAutomatorTestCase; import com.arm.wlauto.uiauto.UxPerfUiAutomation; -import java.io.File; -import java.io.FileWriter; -import java.io.BufferedWriter; import java.util.concurrent.TimeUnit; import java.util.Iterator; import java.util.LinkedHashMap; @@ -33,8 +27,6 @@ public class UiAutomation extends UxPerfUiAutomation { private LinkedHashMap<String, Timer> timingResults = new LinkedHashMap<String, Timer>(); public void runUiAutomation() throws Exception { - Timer result = new Timer(); - result.start(); parameters = getParams(); dismissWelcomeView(); @@ -45,12 +37,9 @@ public class UiAutomation extends UxPerfUiAutomation { gesturesTest("Getting Started.pdf"); - String [] searchStrings = {"Glossary", "cortex"}; + String[] searchStrings = {"Glossary", "cortex"}; searchPdfTest("cortex_m4", searchStrings); - result.end(); - timingResults.put("Total", result); - writeResultsToFile(timingResults, parameters.getString("output_file")); } @@ -73,7 +62,7 @@ public class UiAutomation extends UxPerfUiAutomation { do { i += 10; tapDisplay(webViewCoords.centerX(), webViewCoords.centerY() + i); - } while ( welcomeView.exists() || i < webViewCoords.top ); + } while (welcomeView.exists() || i < webViewCoords.top); } private void signInOnline(Bundle parameters) throws Exception { @@ -139,10 +128,10 @@ public class UiAutomation extends UxPerfUiAutomation { .className("android.widget.Button")); if (allowButton.waitForExists(timeout)) { allowButton.clickAndWaitForNewWindow(timeout); - }; + } } - private void openFile(String filename) throws Exception { + private void openFile(final String filename) throws Exception { String TestTag = "openfile"; @@ -184,7 +173,7 @@ public class UiAutomation extends UxPerfUiAutomation { return result; } - private Timer searchFileList(String searchText) throws Exception { + private Timer searchFileList(final String searchText) throws Exception { // Enter search text into the file searchBox. This will automatically filter the list. UiObject searchBox = getUiObjectByResourceId("android:id/search_src_text", "android.widget.EditText"); @@ -196,7 +185,7 @@ public class UiAutomation extends UxPerfUiAutomation { return result; } - private Timer openFileFromList(String file) throws Exception { + private Timer openFileFromList(final String file) throws Exception { // Open a file from a file list view by searching for UiObjects containing the doc title. UiObject fileObject = getUiObjectByText(file, "android.widget.TextView"); Timer result = new Timer(); @@ -212,31 +201,7 @@ public class UiAutomation extends UxPerfUiAutomation { return result; } - private class GestureTestParams { - GestureType gestureType; - Direction gestureDirection; - PinchType pinchType; - int percent; - int steps; - - GestureTestParams(GestureType gesture, Direction direction, int steps) { - this.gestureType = gesture; - this.gestureDirection = direction; - this.pinchType = PinchType.NULL; - this.steps = steps; - this.percent = 0; - } - - GestureTestParams(GestureType gesture, PinchType pinchType, int steps, int percent) { - this.gestureType = gesture; - this.gestureDirection = Direction.NULL; - this.pinchType = pinchType; - this.steps = steps; - this.percent = percent; - } - } - - private void gesturesTest (String filename) throws Exception { + private void gesturesTest(final String filename) throws Exception { String TestTag = "gestures"; @@ -268,8 +233,8 @@ public class UiAutomation extends UxPerfUiAutomation { UiObject view = new UiObject(new UiSelector().resourceId("com.adobe.reader:id/viewPager")); - startDumpsysGfxInfo(); - startDumpsysSurfaceFlinger(viewName); + startDumpsysGfxInfo(parameters); + startDumpsysSurfaceFlinger(parameters, viewName); Timer results = new Timer(); @@ -287,8 +252,8 @@ public class UiAutomation extends UxPerfUiAutomation { break; } - stopDumpsysSurfaceFlinger(viewName, surfFlingerlogName); - stopDumpsysGfxInfo(gfxInfologName); + stopDumpsysSurfaceFlinger(parameters, viewName, surfFlingerlogName); + stopDumpsysGfxInfo(parameters, gfxInfologName); timingResults.put(runName, results); } @@ -296,7 +261,7 @@ public class UiAutomation extends UxPerfUiAutomation { exitDocument(); } - private void searchPdfTest (String filename, String [] searchStrings) throws Exception { + private void searchPdfTest(final String filename, final String[] searchStrings) throws Exception { String TestTag = "search"; @@ -305,7 +270,7 @@ public class UiAutomation extends UxPerfUiAutomation { // Get the page view for the opened document which we can use for pinch actions UiObject pageView = getUiObjectByResourceId("com.adobe.reader:id/pageView", "android.widget.RelativeLayout"); - for ( int i=0; i < searchStrings.length; i++) { + for (int i = 0; i < searchStrings.length; i++) { timingResults.put(String.format(TestTag + "_" + i), searchTest(searchStrings[i])); } @@ -313,7 +278,7 @@ public class UiAutomation extends UxPerfUiAutomation { exitDocument(); } - private Timer searchTest(String searchText) throws Exception { + private Timer searchTest(final String searchText) throws Exception { // Click on the search button icon and enter text in the box. This closes the keyboad // so click the box again and press Enter to start the search. UiObject searchButton = getUiObjectByResourceId("com.adobe.reader:id/document_view_search_icon", @@ -356,47 +321,4 @@ public class UiAutomation extends UxPerfUiAutomation { UiObject upButton = getUiObjectByResourceId("android:id/up", "android.widget.ImageView" ); upButton.clickAndWaitForNewWindow(); } - - private void writeResultsToFile(LinkedHashMap timingResults, String file) throws Exception { - // Write out the key/value pairs to the instrumentation log file - FileWriter fstream = new FileWriter(file); - BufferedWriter out = new BufferedWriter(fstream); - Iterator<Entry<String, Timer>> it = timingResults.entrySet().iterator(); - - while (it.hasNext()) { - Map.Entry<String, Timer> pairs = it.next(); - Timer results = pairs.getValue(); - long start = results.getStart(); - long finish = results.getFinish(); - long duration = results.getDuration(); - out.write(String.format(pairs.getKey() + " " + start + " " + finish + " " + duration + "\n")); - } - out.close(); - } - - private void startDumpsysSurfaceFlinger(String view) { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - initDumpsysSurfaceFlinger(parameters.getString("package"), view); - } - } - - private void stopDumpsysSurfaceFlinger(String view, String filename) throws Exception { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - File out_file = new File(parameters.getString("output_dir"), filename); - exitDumpsysSurfaceFlinger(parameters.getString("package"), view, out_file); - } - } - - private void startDumpsysGfxInfo() { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - initDumpsysGfxInfo(parameters.getString("package")); - } - } - - private void stopDumpsysGfxInfo(String filename) throws Exception { - if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { - File out_file = new File(parameters.getString("output_dir"), filename); - exitDumpsysGfxInfo(parameters.getString("package"), out_file); - } - } }