-
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.
Exploration of AccessibilityService and instrumentation from tests
- Loading branch information
1 parent
fb9c9a0
commit 1796c0b
Showing
39 changed files
with
1,362 additions
and
0 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
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,28 @@ | ||
apply plugin: 'com.android.application' | ||
|
||
android { | ||
compileSdkVersion 23 | ||
buildToolsVersion "19.1.0" | ||
|
||
defaultConfig { | ||
applicationId "efokschaner.infinityloopsolver" | ||
minSdkVersion 23 | ||
targetSdkVersion 23 | ||
versionCode 1 | ||
versionName "1.0" | ||
} | ||
buildTypes { | ||
release { | ||
minifyEnabled false | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
compile fileTree(dir: 'libs', include: ['*.jar']) | ||
testCompile 'junit:junit:4.12' | ||
compile 'com.android.support:appcompat-v7:23.1.1' | ||
compile 'com.android.support:support-v4:23.1.1' | ||
compile 'com.android.support:design:23.1.1' | ||
} |
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,17 @@ | ||
# Add project specific ProGuard rules here. | ||
# By default, the flags in this file are appended to flags specified | ||
# in /Users/efokschaner/Library/Android/sdk/tools/proguard/proguard-android.txt | ||
# You can edit the include path and order by changing the proguardFiles | ||
# directive in build.gradle. | ||
# | ||
# For more details, see | ||
# http://developer.android.com/guide/developing/tools/proguard.html | ||
|
||
# Add any project specific keep options here: | ||
|
||
# If your project uses WebView with JS, uncomment the following | ||
# and specify the fully qualified class name to the JavaScript interface | ||
# class: | ||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
# public *; | ||
#} |
13 changes: 13 additions & 0 deletions
13
app/src/androidTest/java/efokschaner/infinityloopsolver/ApplicationTest.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,13 @@ | ||
package efokschaner.infinityloopsolver; | ||
|
||
import android.app.Application; | ||
import android.test.ApplicationTestCase; | ||
|
||
/** | ||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> | ||
*/ | ||
public class ApplicationTest extends ApplicationTestCase<Application> { | ||
public ApplicationTest() { | ||
super(Application.class); | ||
} | ||
} |
136 changes: 136 additions & 0 deletions
136
app/src/androidTest/java/efokschaner/infinityloopsolver/UiAutomationTest.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,136 @@ | ||
package efokschaner.infinityloopsolver; | ||
|
||
|
||
import android.accessibilityservice.AccessibilityServiceInfo; | ||
import android.app.Instrumentation; | ||
import android.app.UiAutomation; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.content.pm.PackageManager; | ||
import android.graphics.Bitmap; | ||
import android.test.InstrumentationTestCase; | ||
import android.util.Log; | ||
import android.view.accessibility.AccessibilityEvent; | ||
import android.view.accessibility.AccessibilityNodeInfo; | ||
import android.view.accessibility.AccessibilityWindowInfo; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.io.OutputStream; | ||
import java.net.HttpURLConnection; | ||
import java.net.MalformedURLException; | ||
import java.net.ProtocolException; | ||
import java.net.URL; | ||
import java.text.SimpleDateFormat; | ||
import java.util.Date; | ||
import java.util.List; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
|
||
// Try using Context.startInstrumentation to move this functionality back into app | ||
// Need to derive from Instrumentation class itself and implement onStart (see InstrumentationTestRunner) | ||
public class UiAutomationTest extends InstrumentationTestCase { | ||
private static final String TAG = UiAutomationTest.class.getSimpleName(); | ||
|
||
private static final Runnable NOOP = new Runnable() { public void run() {} }; | ||
|
||
private void sendBitmap(Bitmap bitmap) { | ||
try { | ||
String timestamp = new SimpleDateFormat("HH_mm_ss").format(new Date()); | ||
URL url = new URL("http://10.0.2.2:8888/" + timestamp + ".png"); | ||
try { | ||
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | ||
try { | ||
conn.setDoOutput(true); | ||
conn.setChunkedStreamingMode(0); | ||
conn.setRequestMethod("POST"); | ||
conn.setRequestProperty("Content-Type", "application/octet-stream"); | ||
try (OutputStream ostream = conn.getOutputStream()) { | ||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, ostream); | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
} | ||
final int responseCode = conn.getResponseCode(); | ||
if (!(responseCode >= 200 && responseCode < 300)) { | ||
throw new AssertionError(String.format("Http response was: %d", responseCode)); | ||
} | ||
conn.getResponseMessage(); | ||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); | ||
while (in.readLine() != null){ | ||
// ignore contents | ||
} | ||
in.close(); | ||
} catch (ProtocolException e) { | ||
e.printStackTrace(); | ||
} finally { | ||
conn.disconnect(); | ||
} | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
} | ||
} catch (MalformedURLException e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
private static AccessibilityNodeInfo findInfinityLoopView(AccessibilityNodeInfo node) { | ||
final String viewIdResourceName = node.getViewIdResourceName(); | ||
if(viewIdResourceName != null && viewIdResourceName.equals("com.balysv.loop:id/game_scene_view_light")) { | ||
return node; | ||
} | ||
final int numChildren = node.getChildCount(); | ||
for(int i = 0; i < numChildren; ++i) { | ||
AccessibilityNodeInfo childNode; | ||
if((childNode = findInfinityLoopView(node.getChild(i))) != null) { | ||
return childNode; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private static boolean isInfinityLoopReady(List<AccessibilityWindowInfo> windows) { | ||
// Determine if InfinityLoop (and only InfinityLoop) is on screen | ||
return (windows.size() == 1 && | ||
(findInfinityLoopView(windows.get(0).getRoot())) != null); | ||
} | ||
|
||
private static boolean isInfinityLoopReady(UiAutomation uiAutomation, AccessibilityEvent event) { | ||
if(event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED) { | ||
final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows(); | ||
return isInfinityLoopReady(windows); | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
public void test() throws TimeoutException { | ||
Log.d(TAG, "test()"); | ||
final Instrumentation instrumentation = getInstrumentation(); | ||
final UiAutomation uiAutomation = instrumentation.getUiAutomation(); | ||
final AccessibilityServiceInfo serviceInfo = uiAutomation.getServiceInfo(); | ||
serviceInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; | ||
uiAutomation.setServiceInfo(serviceInfo); | ||
Log.d(TAG, uiAutomation.getServiceInfo().toString()); | ||
final Context context = instrumentation.getContext(); | ||
final PackageManager packageManager = context.getPackageManager(); | ||
final Intent launchIntent = packageManager.getLaunchIntentForPackage("com.balysv.loop"); | ||
if(launchIntent != null) { | ||
context.startActivity(launchIntent); | ||
} | ||
if(!isInfinityLoopReady(uiAutomation.getWindows())) { | ||
uiAutomation.executeAndWaitForEvent(NOOP, new UiAutomation.AccessibilityEventFilter() { | ||
@Override | ||
public boolean accept(AccessibilityEvent event) { | ||
return isInfinityLoopReady(uiAutomation, event); | ||
} | ||
}, 10000); | ||
} | ||
Bitmap b = uiAutomation.takeScreenshot(); | ||
try{ | ||
sendBitmap(b); | ||
} finally { | ||
b.recycle(); | ||
} | ||
} | ||
} |
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,52 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="efokschaner.infinityloopsolver"> | ||
|
||
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" /> | ||
<uses-permission android:name="android.permission.INTERNET" /> | ||
|
||
<application | ||
android:allowBackup="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="@string/app_name" | ||
android:supportsRtl="true" | ||
android:theme="@style/AppTheme"> | ||
|
||
<activity | ||
android:name=".MainActivity" | ||
android:launchMode="singleTask"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
</activity> | ||
|
||
<service | ||
android:name=".AccessibilityService" | ||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> | ||
<intent-filter> | ||
<action android:name="android.accessibilityservice.AccessibilityService" /> | ||
</intent-filter> | ||
|
||
<meta-data | ||
android:name="android.accessibilityservice" | ||
android:resource="@xml/accessibility_service_config" /> | ||
</service> | ||
|
||
<!-- | ||
<activity | ||
android:name=".SettingsActivity" | ||
android:label="@string/title_activity_settings" /> | ||
--> | ||
<activity | ||
android:name=".MediaProjectionRequest" | ||
android:theme="@android:style/Theme.Translucent.NoTitleBar" /> | ||
|
||
<service | ||
android:name=".SolverService" | ||
android:enabled="true" | ||
android:exported="false" /> | ||
|
||
</application> | ||
|
||
</manifest> |
82 changes: 82 additions & 0 deletions
82
app/src/main/java/efokschaner/infinityloopsolver/AccessibilityService.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,82 @@ | ||
package efokschaner.infinityloopsolver; | ||
|
||
import android.content.ComponentName; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.content.ServiceConnection; | ||
import android.os.IBinder; | ||
import android.util.Log; | ||
import android.view.accessibility.AccessibilityEvent; | ||
import android.view.accessibility.AccessibilityNodeInfo; | ||
import android.view.accessibility.AccessibilityWindowInfo; | ||
|
||
import java.util.List; | ||
|
||
|
||
public class AccessibilityService extends android.accessibilityservice.AccessibilityService { | ||
private static final String TAG = AccessibilityService.class.getSimpleName(); | ||
|
||
private AccessibilityNodeInfo mGameView; | ||
private SolverService mSolverService; | ||
private ServiceConnection mConnection = new ServiceConnection() { | ||
@Override | ||
public void onServiceConnected(ComponentName name, IBinder service) { | ||
mSolverService = ((SolverService.SolverServiceBinder)service).getService(); | ||
} | ||
|
||
@Override | ||
public void onServiceDisconnected(ComponentName name) { | ||
mSolverService = null; | ||
} | ||
}; | ||
|
||
@Override | ||
protected void onServiceConnected() { | ||
Log.d(TAG, "onServiceConnected"); | ||
super.onServiceConnected(); | ||
bindService(new Intent(this, SolverService.class), mConnection, Context.BIND_AUTO_CREATE); | ||
} | ||
|
||
private AccessibilityNodeInfo findInfinityLoopView(AccessibilityNodeInfo node) { | ||
final String viewIdResourceName = node.getViewIdResourceName(); | ||
if(viewIdResourceName != null && viewIdResourceName.equals("com.balysv.loop:id/game_scene_view_light")) { | ||
return node; | ||
} | ||
final int numChildren = node.getChildCount(); | ||
for(int i = 0; i < numChildren; ++i) { | ||
AccessibilityNodeInfo childNode; | ||
if((childNode = findInfinityLoopView(node.getChild(i))) != null) { | ||
return childNode; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public void onAccessibilityEvent(AccessibilityEvent event) { | ||
if(mSolverService == null) { | ||
// Ignore all events until we're connected to the solver service | ||
return; | ||
} | ||
if(event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED) { | ||
final List<AccessibilityWindowInfo> windows = getWindows(); | ||
// Determine if InfinityLoop (and only InfinityLoop) is on screen | ||
if(windows.size() == 1 && (mGameView = findInfinityLoopView(windows.get(0).getRoot())) != null) { | ||
mSolverService.SetInfinityLoopIsFocused(true); | ||
} else { | ||
mSolverService.SetInfinityLoopIsFocused(false); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void onInterrupt() { | ||
} | ||
|
||
@Override | ||
public boolean onUnbind(Intent i) { | ||
Log.d(TAG, "onUnbind"); | ||
unbindService(mConnection); | ||
return false; | ||
} | ||
} |
Oops, something went wrong.