├── app
├── .gitignore
├── src
│ └── main
│ │ ├── assets
│ │ ├── helloworld.bsh
│ │ └── toast.bsh
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── styles.xml
│ │ │ └── strings.xml
│ │ └── layout
│ │ │ └── main_activity.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── jwf
│ │ └── debugport
│ │ └── app
│ │ ├── App.java
│ │ └── MainActivity.java
├── proguard-rules.pro
└── build.gradle
├── lib
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── ids.xml
│ │ │ └── strings.xml
│ │ └── drawable
│ │ │ └── debugport_ic_notification.xml
│ │ ├── java
│ │ └── jwf
│ │ │ └── debugport
│ │ │ ├── internal
│ │ │ ├── debug
│ │ │ │ ├── commands
│ │ │ │ │ ├── exit.java
│ │ │ │ │ ├── source.java
│ │ │ │ │ ├── get.java
│ │ │ │ │ ├── set.java
│ │ │ │ │ ├── Commands.java
│ │ │ │ │ ├── fields.java
│ │ │ │ │ ├── methods.java
│ │ │ │ │ ├── fieldsLocal.java
│ │ │ │ │ ├── methodsLocal.java
│ │ │ │ │ ├── descriptors
│ │ │ │ │ │ ├── FieldDescriptor.java
│ │ │ │ │ │ ├── MethodDescriptor.java
│ │ │ │ │ │ └── MemberDescriptor.java
│ │ │ │ │ ├── help.java
│ │ │ │ │ └── call.java
│ │ │ │ ├── DebugTelnetServer.java
│ │ │ │ ├── TelnetConsoleInterface.java
│ │ │ │ └── DebugClientConnection.java
│ │ │ ├── sqlite
│ │ │ │ ├── SQLiteTelnetServer.java
│ │ │ │ ├── SQLiteOpenHelper.java
│ │ │ │ ├── commands
│ │ │ │ │ ├── ExitCommand.java
│ │ │ │ │ ├── SQLiteCommand.java
│ │ │ │ │ ├── Commands.java
│ │ │ │ │ ├── ShowTablesCommand.java
│ │ │ │ │ ├── ShowDatabasesCommand.java
│ │ │ │ │ ├── UseCommand.java
│ │ │ │ │ ├── ShowCreateTableCommand.java
│ │ │ │ │ ├── DropDatabaseCommand.java
│ │ │ │ │ ├── CreateDatabaseCommand.java
│ │ │ │ │ └── HelpCommand.java
│ │ │ │ └── SQLiteClientConnection.java
│ │ │ ├── ClientConnection.java
│ │ │ ├── TelnetServer.java
│ │ │ ├── Utils.java
│ │ │ └── CommandHelpInfo.java
│ │ │ ├── annotations
│ │ │ └── Command.java
│ │ │ ├── DebugPortContentProvider.java
│ │ │ ├── Params.java
│ │ │ └── DebugPortService.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── lib-noop
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── jwf
│ │ └── debugport
│ │ ├── DebugPortService.java
│ │ └── Params.java
├── proguard-rules.pro
└── build.gradle
├── .gitignore
├── LICENSE
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/lib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/src/main/assets/helloworld.bsh:
--------------------------------------------------------------------------------
1 | print("hello world");
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':lib', ':lib-noop'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonwyatt/Android-DebugPort/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/lib-noop/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonwyatt/Android-DebugPort/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonwyatt/Android-DebugPort/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonwyatt/Android-DebugPort/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonwyatt/Android-DebugPort/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonwyatt/Android-DebugPort/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/lib/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .idea/
10 | */build
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue May 09 13:48:22 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/assets/toast.bsh:
--------------------------------------------------------------------------------
1 | import android.os.Handler;
2 | import android.os.Looper;
3 | import android.widget.Toast;
4 |
5 | showToast(text) {
6 | new Handler(Looper.getMainLooper()).post(new Runnable() {
7 | public void run() {
8 | Toast.makeText(app, text, Toast.LENGTH_LONG).show();
9 | }
10 | });
11 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lib/src/main/res/drawable/debugport_ic_notification.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Jason Feinstein
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 |
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Android DebugPort Testapp
3 | stop server
4 | start server
5 | Debug server running at %1$s:%2$d
6 | SQLite server running at %1$s:%2$d
7 |
8 | - import android.os.*;
9 | - import java.util.*;
10 | - x = 1+1;
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/exit.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import bsh.CallStack;
4 | import bsh.EvalError;
5 | import bsh.Interpreter;
6 | import jwf.debugport.annotations.Command;
7 |
8 | /**
9 | *
10 | */
11 | @Command
12 | public class exit {
13 | @Command.Help("Exit this interpreter.")
14 | public static void invoke(Interpreter env, CallStack callStack) {
15 | env.println("Thanks for using Android DebugPort!");
16 | try {
17 | ((Commands)env.get("cmd")).exit();
18 | } catch (EvalError evalError) {
19 | env.error(evalError.toString());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jason/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/lib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jason/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion '26.0.1'
6 |
7 | defaultConfig {
8 | applicationId "jwf.debugport.app"
9 | minSdkVersion 14
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:26.1.0'
26 |
27 | debugCompile project(':lib')
28 | releaseCompile project(':lib-noop')
29 | }
30 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/DebugTelnetServer.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug;
2 |
3 | import android.content.Context;
4 |
5 | import java.net.Socket;
6 | import java.net.UnknownHostException;
7 |
8 | import jwf.debugport.Params;
9 | import jwf.debugport.internal.TelnetServer;
10 |
11 | /**
12 | * Implementation of {@link TelnetServer} for a REPL in the Application context.
13 | */
14 | public class DebugTelnetServer extends TelnetServer {
15 | public DebugTelnetServer(Context app, Params params) throws UnknownHostException {
16 | super(app, params, params.getDebugPort());
17 | }
18 |
19 | @Override
20 | public DebugClientConnection getClientConnection(Context c, Socket socket, TelnetServer server, String[] startupCommands) {
21 | return new DebugClientConnection(c, socket, server, startupCommands);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Android DebugPort
3 | Android DebugPort: %s
4 | Debug Running at %1$s:%2$d\nSQLite Running at %1$s:%3$d
5 | Stopped
6 | Debug: %1$s:%2$d, SQLite: %1$s:%3$d
7 | Stopped
8 | Stop
9 | Start
10 | Kill
11 | Android DebugPort
12 |
13 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/SQLiteTelnetServer.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite;
2 |
3 | import android.content.Context;
4 |
5 | import java.net.Socket;
6 | import java.net.UnknownHostException;
7 |
8 | import jwf.debugport.Params;
9 | import jwf.debugport.internal.TelnetServer;
10 |
11 | /**
12 | * Implementation of {@link TelnetServer} to support a REPL to access SQLite Databases.
13 | */
14 | public class SQLiteTelnetServer extends TelnetServer {
15 | public SQLiteTelnetServer(Context app, Params params) throws UnknownHostException {
16 | super(app, params, params.getSQLitePort());
17 | }
18 |
19 | @Override
20 | public SQLiteClientConnection getClientConnection(Context c, Socket socket, TelnetServer server, String[] startupCommands) {
21 | return new SQLiteClientConnection(c, socket, server, startupCommands);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
12 |
17 |
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/lib-noop/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jason/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/SQLiteOpenHelper.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.database.sqlite.SQLiteDatabase;
6 |
7 | /**
8 | * Created by jason on 5/9/16.
9 | */
10 | public class SQLiteOpenHelper {
11 | private final Application mApp;
12 | private final String mName;
13 | private boolean mCreated;
14 | private SQLiteDatabase mDatabase;
15 |
16 | public SQLiteOpenHelper(SQLiteClientConnection connection, String name) {
17 | mApp = connection.getApp();
18 | mName = name;
19 | }
20 |
21 | public SQLiteDatabase open() {
22 | if (mDatabase == null) {
23 | mDatabase = mApp.openOrCreateDatabase(mName, Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, null);
24 | }
25 | return mDatabase;
26 | }
27 |
28 | public void close() {
29 | if (mDatabase != null && mDatabase.isOpen()) {
30 | mDatabase.close();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/source.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import android.app.Application;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStreamReader;
7 | import java.io.Reader;
8 |
9 | import bsh.CallStack;
10 | import bsh.EvalError;
11 | import bsh.Interpreter;
12 | import jwf.debugport.annotations.Command;
13 |
14 | /**
15 | *
16 | */
17 | @Command
18 | public class source {
19 | @Command.Help("Load and run a Beanshell script within your app's assets folder.")
20 | public static Object invoke(
21 | Interpreter interpreter,
22 | CallStack callStack,
23 | @Command.ParamName("scriptPath") String assetScriptPath) throws EvalError, IOException {
24 | Application app = (Application) interpreter.get("app");
25 | Reader scriptIn = new InputStreamReader(app.getAssets().open(assetScriptPath));
26 | Object result = interpreter.eval(scriptIn);
27 | scriptIn.close();
28 | return result;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib-noop/src/main/java/jwf/debugport/DebugPortService.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.IBinder;
7 |
8 | /**
9 | * No-op implementation of DebugPortService.
10 | */
11 | public class DebugPortService extends Service {
12 | public static final String METADATA_DEBUG_PORT = "jwf.debugport.METADATA_DEBUG_PORT";
13 | public static final String METADATA_SQLITE_PORT = "jwf.debugport.METADATA_SQLITE_PORT";
14 | public static final String METADATA_STARTUP_COMMANDS = "jwf.debugport.METADATA_STARTUP_COMMANDS";
15 |
16 | public static Params start(Context context) {
17 | Params params = new Params();
18 | return params;
19 | }
20 |
21 | public static void start(Context context, Params params) {
22 | }
23 |
24 | public static void stop(Context context) {
25 | }
26 |
27 | public static void kill(Context context) {
28 | }
29 |
30 | @Override
31 | public IBinder onBind(Intent intent) {
32 | return null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/get.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import bsh.CallStack;
6 | import bsh.Interpreter;
7 | import jwf.debugport.annotations.Command;
8 |
9 | /**
10 | *
11 | */
12 | @Command(group = Command.GROUP_ACCESS)
13 | public class get {
14 | @Command.Help("Get the value of a field, regardless of access modifiers, on the provided object.")
15 | public static Object invoke(
16 | Interpreter interpreter,
17 | CallStack callstack,
18 | @Command.ParamName("obj") Object object,
19 | @Command.ParamName("fieldName") String param) throws NoSuchFieldException, IllegalAccessException {
20 | Field field;
21 | try {
22 | field = object.getClass().getDeclaredField(param);
23 | } catch (NoSuchFieldException e) {
24 | // let's try a regular field..
25 | field = object.getClass().getField(param);
26 | }
27 | field.setAccessible(true);
28 | return field.get(object);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/ExitCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import java.io.PrintWriter;
4 | import java.util.regex.Pattern;
5 |
6 | import jwf.debugport.annotations.Command;
7 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
8 |
9 | /**
10 | * Created by jason on 5/9/16.
11 | */
12 | @Command.Help(
13 | value = "Exit this interpreter.",
14 | format = "exit; or quit;"
15 | )
16 | public class ExitCommand extends SQLiteCommand {
17 | private static final Pattern command = Pattern.compile("^\\s*exit|quit\\s*$", Pattern.CASE_INSENSITIVE);
18 |
19 | @Override
20 | public boolean isCommandString(String candidate) {
21 | return command.matcher(candidate).matches();
22 | }
23 |
24 | @Override
25 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
26 | out.println("Thanks for using Android DebugPort!");
27 | out.flush();
28 | }
29 |
30 | @Override
31 | public boolean isExitCommand() {
32 | return true;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib-noop/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'maven'
3 |
4 | group='com.github.jasonwyatt'
5 |
6 | android {
7 | compileSdkVersion 26
8 | buildToolsVersion "26.0.1"
9 |
10 | defaultConfig {
11 | minSdkVersion 14
12 | targetSdkVersion 26
13 | versionCode 5
14 | versionName "2.1.0"
15 | }
16 | }
17 |
18 | dependencies {
19 | compile fileTree(dir: 'libs', include: ['*.jar'])
20 | testCompile 'junit:junit:4.12'
21 | }
22 |
23 | // build a jar with source files
24 | task sourcesJar(type: Jar) {
25 | from android.sourceSets.main.java.srcDirs
26 | classifier = 'sources'
27 | }
28 |
29 | task javadoc(type: Javadoc) {
30 | failOnError false
31 | source = android.sourceSets.main.java.sourceFiles
32 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
33 | classpath += configurations.compile
34 | }
35 |
36 | // build a jar with javadoc
37 | task javadocJar(type: Jar, dependsOn: javadoc) {
38 | classifier = 'javadoc'
39 | from javadoc.destinationDir
40 | }
41 |
42 | artifacts {
43 | archives sourcesJar
44 | archives javadocJar
45 | }
46 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/annotations/Command.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | *
10 | */
11 | @Target(ElementType.TYPE)
12 | @Retention(RetentionPolicy.RUNTIME)
13 | public @interface Command {
14 | String GROUP_METHOD_INSPECTION = "Method Inspection";
15 | String GROUP_FIELD_INSPECTION = "Field Inspection";
16 | String GROUP_ACCESS = "Access";
17 | String GROUP_SQL_INSPECTION = "Inspection";
18 | String GROUP_SQL_DATABASES = "Databases";
19 | String GROUP_OTHER = "Other";
20 |
21 | String group() default GROUP_OTHER;
22 |
23 | @Target({ElementType.METHOD, ElementType.TYPE})
24 | @Retention(RetentionPolicy.RUNTIME)
25 | @interface Help {
26 | String value() default "";
27 | String group() default GROUP_OTHER;
28 | String format() default "";
29 | }
30 |
31 | @Target(ElementType.PARAMETER)
32 | @Retention(RetentionPolicy.RUNTIME)
33 | @interface ParamName {
34 | String value() default "";
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/set.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import bsh.CallStack;
6 | import bsh.Interpreter;
7 | import jwf.debugport.annotations.Command;
8 |
9 | /**
10 | *
11 | */
12 | @Command(group = Command.GROUP_ACCESS)
13 | public class set {
14 | @Command.Help("Set the value of a field on the provided object to the given value, regardless of access modifiers.")
15 | public static Object invoke(
16 | Interpreter interpreter,
17 | CallStack callStack,
18 | @Command.ParamName("obj") Object object,
19 | @Command.ParamName("fieldName") String param,
20 | @Command.ParamName("value") Object value) throws NoSuchFieldException, IllegalAccessException {
21 | Field field;
22 | try {
23 | field = object.getClass().getDeclaredField(param);
24 | } catch (NoSuchFieldException e) {
25 | // try a field on the inherited classes
26 | field = object.getClass().getField(param);
27 | }
28 | field.setAccessible(true);
29 | field.set(object, value);
30 | return value;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'maven'
3 |
4 | group='com.github.jasonwyatt'
5 |
6 | android {
7 | compileSdkVersion 26
8 | buildToolsVersion '26.0.1'
9 |
10 | defaultConfig {
11 | minSdkVersion 14
12 | targetSdkVersion 26
13 | versionCode 5
14 | versionName "2.1.0"
15 | }
16 |
17 | lintOptions {
18 | disable 'InvalidPackage'
19 | }
20 | }
21 |
22 | dependencies {
23 | compile "org.apache-extras.beanshell:bsh:2.0b6"
24 | compile "com.android.support:support-compat:26.1.0"
25 | }
26 |
27 | // build a jar with source files
28 | task sourcesJar(type: Jar) {
29 | from android.sourceSets.main.java.srcDirs
30 | classifier = 'sources'
31 | }
32 |
33 | task javadoc(type: Javadoc) {
34 | failOnError false
35 | source = android.sourceSets.main.java.sourceFiles
36 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
37 | classpath += configurations.compile
38 | }
39 |
40 | // build a jar with javadoc
41 | task javadocJar(type: Jar, dependsOn: javadoc) {
42 | classifier = 'javadoc'
43 | from javadoc.destinationDir
44 | }
45 |
46 | artifacts {
47 | archives sourcesJar
48 | archives javadocJar
49 | }
50 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/SQLiteCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import java.io.PrintWriter;
4 | import java.util.regex.Pattern;
5 |
6 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
7 |
8 | /**
9 | * Abstract command..
10 | */
11 | public abstract class SQLiteCommand {
12 | protected static final Pattern DATABASE_NAME = Pattern.compile("[a-zA-Z][_a-zA-Z0-9]*");
13 | protected static final Pattern TABLE_NAME = Pattern.compile("[a-zA-Z][_a-zA-Z0-9]*");
14 |
15 | /**
16 | * Whether or not the given string is a match for this command.
17 | */
18 | public abstract boolean isCommandString(String candidate);
19 |
20 | /**
21 | * Execute the command.
22 | */
23 | public abstract void execute(SQLiteClientConnection connection, PrintWriter out, String commandString);
24 |
25 | public boolean isExitCommand() {
26 | return false;
27 | }
28 |
29 | protected final boolean isValidDatabaseName(String dbName) {
30 | return DATABASE_NAME.matcher(dbName).matches();
31 | }
32 |
33 | protected final boolean isValidTableName(String tableName) {
34 | return TABLE_NAME.matcher(tableName).matches();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/DebugPortContentProvider.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport;
2 |
3 | import android.content.ContentProvider;
4 | import android.content.ContentValues;
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 |
8 | /**
9 | * Simple ContentProvider-based approach to initializing the DebugPort service.
10 | * @author jason
11 | */
12 |
13 | public class DebugPortContentProvider extends ContentProvider {
14 | @Override
15 | public boolean onCreate() {
16 | DebugPortService.initialize(getContext());
17 | return true;
18 | }
19 |
20 | @Override
21 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
22 | return null;
23 | }
24 |
25 | @Override
26 | public String getType(Uri uri) {
27 | return null;
28 | }
29 |
30 | @Override
31 | public Uri insert(Uri uri, ContentValues values) {
32 | return null;
33 | }
34 |
35 | @Override
36 | public int delete(Uri uri, String selection, String[] selectionArgs) {
37 | return 0;
38 | }
39 |
40 | @Override
41 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
42 | return 0;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/Commands.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import java.lang.ref.WeakReference;
4 |
5 | /**
6 | *
7 | */
8 | public final class Commands {
9 | private final WeakReference mListener;
10 |
11 | static {
12 | // helper block to initialize all the commands... TODO: maybe a better way to do this?
13 | help.registerCommand(help.class);
14 | help.registerCommand(exit.class);
15 | help.registerCommand(methods.class);
16 | help.registerCommand(methodsLocal.class);
17 | help.registerCommand(fields.class);
18 | help.registerCommand(fieldsLocal.class);
19 | help.registerCommand(get.class);
20 | help.registerCommand(set.class);
21 | help.registerCommand(call.class);
22 | help.registerCommand(source.class);
23 | }
24 |
25 | public Commands(ExitListener listener) {
26 | mListener = new WeakReference<>(listener);
27 | }
28 |
29 | public void exit() {
30 | ExitListener listener = mListener.get();
31 | if (listener != null) {
32 | listener.onExit();
33 | }
34 | }
35 |
36 | public interface ExitListener {
37 | void onExit();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/Commands.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | /**
4 | *
5 | */
6 | public final class Commands {
7 | private static Commands sInstance;
8 | private final SQLiteCommand[] mAvailableCommands;
9 |
10 | private Commands() {
11 | mAvailableCommands = new SQLiteCommand[] {
12 | new ExitCommand(),
13 | new ShowDatabasesCommand(),
14 | new CreateDatabaseCommand(),
15 | new DropDatabaseCommand(),
16 | new UseCommand(),
17 | new ShowTablesCommand(),
18 | new ShowCreateTableCommand(),
19 | new HelpCommand(),
20 | };
21 | }
22 |
23 | public final SQLiteCommand[] getCommands() {
24 | return mAvailableCommands;
25 | }
26 |
27 | public SQLiteCommand getCommand(String input) {
28 | for (SQLiteCommand cmd : mAvailableCommands) {
29 | if (cmd.isCommandString(input)) {
30 | return cmd;
31 | }
32 | }
33 | return null;
34 | }
35 |
36 | public static final Commands getInstance() {
37 | if (sInstance == null) {
38 | sInstance = new Commands();
39 | }
40 | return sInstance;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/jwf/debugport/app/App.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.app;
2 |
3 | import android.app.Application;
4 | import android.content.Intent;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | import jwf.debugport.DebugPortService;
10 |
11 | /**
12 | *
13 | */
14 | public class App extends Application {
15 | HashMap mStringIntHashmap = new HashMap<>();
16 | public Class>[] mClassArray;
17 | public int[] mIntArray;
18 | public boolean[] mBooleanArray;
19 | public char[] mCharArray;
20 | public short[] mShortArray;
21 | public byte[] mByteArray;
22 | public long[] mLongArray;
23 | private String mSecretString = "This is a private value.";
24 | private int mSecretInt = 42;
25 |
26 | @Override
27 | public void onCreate() {
28 | super.onCreate();
29 | mStringIntHashmap.put("Test", 1);
30 | }
31 |
32 | public Map getStringIntHashmap() {
33 | return mStringIntHashmap;
34 | }
35 |
36 | public void setStringIntHashmap(HashMap map) {
37 | mStringIntHashmap = map;
38 | }
39 |
40 | public int[] getIntArray() {
41 | return mIntArray;
42 | }
43 |
44 | public void setIntArray(int[] arry) {
45 | mIntArray = arry;
46 | }
47 |
48 | public void setIntArrayVarArg(int... arr) {
49 | mIntArray = arr;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/fields.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import bsh.CallStack;
4 | import bsh.Interpreter;
5 | import jwf.debugport.annotations.Command;
6 | import jwf.debugport.internal.Utils;
7 | import jwf.debugport.internal.debug.commands.descriptors.FieldDescriptor;
8 |
9 | /**
10 | *
11 | */
12 | @Command(group = Command.GROUP_FIELD_INSPECTION)
13 | public class fields {
14 | @Command.Help("List all of the fields available for a particular object.")
15 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("obj") Object obj) {
16 | invoke(interpreter, callStack, obj.getClass());
17 | }
18 |
19 | @Command.Help("List all of the fields available for a particular class.")
20 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("class") Class klass) {
21 | if (klass == null) {
22 | interpreter.println("value is null");
23 | return;
24 | }
25 | FieldDescriptor[] fields = FieldDescriptor.fromFields(klass.getFields());
26 | StringBuilder sb = new StringBuilder();
27 |
28 | sb.append("fields {\n");
29 |
30 | for (FieldDescriptor field : fields) {
31 | sb.append(Utils.indent(1));
32 | sb.append(field.toString());
33 | sb.append("\n");
34 | }
35 |
36 | sb.append("}");
37 |
38 | interpreter.println(sb.toString());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/methods.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import bsh.CallStack;
4 | import bsh.Interpreter;
5 | import jwf.debugport.annotations.Command;
6 | import jwf.debugport.internal.Utils;
7 | import jwf.debugport.internal.debug.commands.descriptors.MethodDescriptor;
8 |
9 | /**
10 | *
11 | */
12 | @Command(group = Command.GROUP_METHOD_INSPECTION)
13 | public class methods {
14 | @Command.Help("Get the available methods for the provided object.")
15 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("obj") Object obj) {
16 | invoke(interpreter, callStack, obj.getClass());
17 | }
18 |
19 | @Command.Help("Get the available methods for the provided class.")
20 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("class") Class klass) {
21 | if (klass == null) {
22 | interpreter.println("value is null");
23 | return;
24 | }
25 | MethodDescriptor[] methods = MethodDescriptor.fromMethods(klass.getMethods());
26 | StringBuilder sb = new StringBuilder();
27 |
28 | sb.append("available methods: {\n");
29 | for (MethodDescriptor method : methods) {
30 | sb.append(Utils.indent(1));
31 | sb.append(method.toString());
32 | sb.append("\n");
33 | }
34 | sb.append("}");
35 |
36 | interpreter.println(sb.toString());
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/fieldsLocal.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import bsh.CallStack;
4 | import bsh.Interpreter;
5 | import jwf.debugport.annotations.Command;
6 | import jwf.debugport.internal.Utils;
7 | import jwf.debugport.internal.debug.commands.descriptors.FieldDescriptor;
8 |
9 | /**
10 | *
11 | */
12 | @Command(group = Command.GROUP_FIELD_INSPECTION)
13 | public class fieldsLocal {
14 | @Command.Help("List all of the fields defined locally for an object.")
15 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("obj") Object obj) {
16 | invoke(interpreter, callStack, obj.getClass());
17 | }
18 |
19 | @Command.Help("List all of the fields defined locally for a particular class.")
20 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("class") Class klass) {
21 | if (klass == null) {
22 | interpreter.println("null");
23 | return;
24 | }
25 | FieldDescriptor[] fields = FieldDescriptor.fromFields(klass.getDeclaredFields());
26 | StringBuilder sb = new StringBuilder();
27 |
28 | sb.append("declared fields {\n");
29 |
30 | for (FieldDescriptor field : fields) {
31 | sb.append(Utils.indent(1));
32 | sb.append(field.toString());
33 | sb.append("\n");
34 | }
35 |
36 | sb.append("}");
37 |
38 | interpreter.println(sb.toString());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/ShowTablesCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import android.database.Cursor;
4 | import android.database.sqlite.SQLiteDatabase;
5 |
6 | import java.io.PrintWriter;
7 | import java.util.regex.Pattern;
8 |
9 | import jwf.debugport.annotations.Command;
10 | import jwf.debugport.internal.Utils;
11 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
12 |
13 | /**
14 | * Created by jason on 5/9/16.
15 | */
16 | @Command.Help(
17 | format = "SHOW TABLES;",
18 | value = "Show all of the tables defined for the database to which you are currently connected.",
19 | group = Command.GROUP_SQL_INSPECTION
20 | )
21 | public class ShowTablesCommand extends SQLiteCommand {
22 | public static final Pattern command = Pattern.compile("show\\s+tables", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
23 |
24 | @Override
25 | public boolean isCommandString(String candidate) {
26 | return command.matcher(candidate).matches();
27 | }
28 |
29 | @Override
30 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
31 | SQLiteDatabase db = connection.getCurrentDatabase();
32 | if (db == null) {
33 | connection.printNoDBSelected(out);
34 | return;
35 | }
36 |
37 | Cursor c = db.rawQuery("SELECT name AS 'Table' FROM sqlite_master WHERE type = 'table' ORDER BY name", null);
38 | Utils.printTable(out, c);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/methodsLocal.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import bsh.CallStack;
4 | import bsh.Interpreter;
5 | import jwf.debugport.annotations.Command;
6 | import jwf.debugport.internal.Utils;
7 | import jwf.debugport.internal.debug.commands.descriptors.MethodDescriptor;
8 |
9 | /**
10 | *
11 | */
12 | @Command(group = Command.GROUP_METHOD_INSPECTION)
13 | public class methodsLocal {
14 | @Command.Help("Show all of the locally-declared methods for the provided object.")
15 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("obj") Object obj) {
16 | invoke(interpreter, callStack, obj.getClass());
17 | }
18 |
19 | @Command.Help("Show all of the locally-declared methods for the provided class.")
20 | public static void invoke(Interpreter interpreter, CallStack callStack, @Command.ParamName("class") Class klass) {
21 | if (klass == null) {
22 | interpreter.println("value is null");
23 | return;
24 | }
25 | MethodDescriptor[] methods = MethodDescriptor.fromMethods(klass.getDeclaredMethods());
26 | StringBuilder sb = new StringBuilder();
27 |
28 | sb.append("declared methods: {\n");
29 | for (MethodDescriptor method : methods) {
30 | sb.append(Utils.indent(1));
31 | sb.append(method.toString());
32 | sb.append("\n");
33 | }
34 | sb.append("}");
35 |
36 | interpreter.println(sb.toString());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/ShowDatabasesCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import android.app.Application;
4 |
5 | import java.io.PrintWriter;
6 | import java.util.Arrays;
7 | import java.util.regex.Pattern;
8 |
9 | import jwf.debugport.annotations.Command;
10 | import jwf.debugport.internal.Utils;
11 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
12 |
13 | /**
14 | * Created by jason on 5/9/16.
15 | */
16 | @Command.Help(
17 | format = "SHOW DATABASES;",
18 | value = "Show all available databases for the app, including temporary databases.",
19 | group = Command.GROUP_SQL_INSPECTION
20 | )
21 | public class ShowDatabasesCommand extends SQLiteCommand {
22 | private static final Pattern command = Pattern.compile("show\\s+databases", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
23 | @Override
24 | public boolean isCommandString(String candidate) {
25 | return command.matcher(candidate).matches();
26 | }
27 |
28 | @Override
29 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
30 | Application app = connection.getApp();
31 | String[] databaseList = app.databaseList();
32 | Arrays.sort(databaseList);
33 | Object[][] data = new Object[databaseList.length][1];
34 | for (int i = 0; i < databaseList.length; i++) {
35 | data[i][0] = databaseList[i];
36 | }
37 | Utils.printTable(out, new String[] { "Database" }, data);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/descriptors/FieldDescriptor.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands.descriptors;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.lang.reflect.Field;
6 | import java.lang.reflect.ParameterizedType;
7 | import java.lang.reflect.Type;
8 | import java.util.Arrays;
9 |
10 | /**
11 | *
12 | */
13 | public class FieldDescriptor extends MemberDescriptor {
14 | private final Field mField;
15 |
16 | public FieldDescriptor(Field field) {
17 | super(field);
18 | mField = field;
19 | }
20 |
21 | @Override
22 | public String toString() {
23 | StringBuilder sb = new StringBuilder();
24 |
25 | String modifier = getModifierString();
26 | if (!TextUtils.isDigitsOnly(modifier)) {
27 | sb.append(modifier);
28 | sb.append(" ");
29 | }
30 |
31 | Type type = mField.getType();
32 | sb.append(getSimpleClassName(type));
33 | try {
34 | Type genericType = mField.getGenericType();
35 | if (genericType instanceof ParameterizedType) {
36 | sb.append(getParametrizedTypeString((ParameterizedType) genericType));
37 | }
38 | } catch (Exception e) {
39 | // nothing to do here.
40 | }
41 | sb.append(" ");
42 | sb.append(getName());
43 |
44 | return sb.toString();
45 | }
46 |
47 | public static FieldDescriptor[] fromFields(Field[] fields) {
48 | FieldDescriptor[] result = new FieldDescriptor[fields.length];
49 | for (int i = 0; i < result.length; i++) {
50 | result[i] = new FieldDescriptor(fields[i]);
51 | }
52 | Arrays.sort(result);
53 | return result;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/UseCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import java.io.PrintWriter;
4 | import java.util.regex.Matcher;
5 | import java.util.regex.Pattern;
6 |
7 | import jwf.debugport.annotations.Command;
8 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
9 |
10 | /**
11 | *
12 | */
13 | @Command.Help(
14 | format = "USE [database name];",
15 | value = "Connect to the database called [database name]. All SQL commands will be executed against this database until USE is called again.",
16 | group = Command.GROUP_SQL_DATABASES
17 | )
18 | public class UseCommand extends SQLiteCommand {
19 | private static final Pattern command = Pattern.compile("\\s*use\\s+(\\w+)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
20 |
21 | @Override
22 | public boolean isCommandString(String candidate) {
23 | return command.matcher(candidate).matches();
24 | }
25 |
26 | @Override
27 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
28 | Matcher commandMatcher = command.matcher(commandString);
29 | if (!commandMatcher.matches()) {
30 | return;
31 | }
32 | String dbName = commandMatcher.group(1);
33 |
34 | if (!isValidDatabaseName(dbName)) {
35 | out.println("Invalid database name: "+dbName);
36 | out.flush();
37 | } else {
38 | if (!connection.databaseExists(dbName)) {
39 | out.println("Database `"+dbName+"` does not exist");
40 | return;
41 | }
42 |
43 | connection.setCurrentDatabase(dbName);
44 | out.println("Using database `"+dbName+"`");
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/TelnetConsoleInterface.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug;
2 |
3 | import java.io.Closeable;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.io.OutputStream;
8 | import java.io.PrintStream;
9 | import java.io.Reader;
10 |
11 | import bsh.ConsoleInterface;
12 |
13 | /**
14 | *
15 | */
16 | class TelnetConsoleInterface implements ConsoleInterface, Closeable {
17 | private final InputStream mIn;
18 | private final OutputStream mOut;
19 | private InputStreamReader mReader;
20 | private PrintStream mPrintStream;
21 |
22 | public TelnetConsoleInterface(InputStream inputStream, OutputStream outputStream) {
23 | mIn = inputStream;
24 | mOut = outputStream;
25 | }
26 |
27 | @Override
28 | public Reader getIn() {
29 | if (mReader == null) {
30 | mReader = new InputStreamReader(mIn);
31 | }
32 | return mReader;
33 | }
34 |
35 | @Override
36 | public PrintStream getOut() {
37 | if (mPrintStream == null) {
38 | mPrintStream = new PrintStream(mOut, true);
39 | }
40 | return mPrintStream;
41 | }
42 |
43 | @Override
44 | public PrintStream getErr() {
45 | return getOut();
46 | }
47 |
48 | @Override
49 | public void println(Object o) {
50 | getOut().println(o == null ? "null" : o.toString());
51 | }
52 |
53 | @Override
54 | public void print(Object o) {
55 | getOut().print(o == null ? "null" : o.toString());
56 | }
57 |
58 | @Override
59 | public void error(Object o) {
60 | getErr().print(o == null ? "null" : o.toString());
61 | }
62 |
63 | @Override
64 | public void close() throws IOException {
65 | mIn.close();
66 | mOut.close();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/DebugClientConnection.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug;
2 |
3 | import android.content.Context;
4 |
5 | import java.io.IOException;
6 | import java.net.Socket;
7 |
8 | import bsh.Interpreter;
9 | import jwf.debugport.internal.debug.commands.Commands;
10 | import jwf.debugport.internal.ClientConnection;
11 | import jwf.debugport.internal.TelnetServer;
12 |
13 | /**
14 | *
15 | */
16 | public class DebugClientConnection extends ClientConnection implements Commands.ExitListener {
17 | private TelnetConsoleInterface mConsole;
18 |
19 | public DebugClientConnection(Context context, Socket client, TelnetServer parent, String[] startupCommands) {
20 | super(context, client, parent, startupCommands);
21 | }
22 |
23 | @Override
24 | public void closeConnection() {
25 | // nothing to do here..
26 | }
27 |
28 | @Override
29 | public void run() {
30 | try {
31 | mConsole = new TelnetConsoleInterface(getSocket().getInputStream(), getSocket().getOutputStream());
32 | Interpreter interpreter = new Interpreter(mConsole);
33 | interpreter.setShowResults(true);
34 | interpreter.set("cmd", new Commands(this));
35 | interpreter.set("app", getApp());
36 | interpreter.eval("importCommands(\"jwf.debugport.internal.debug.commands\")");
37 |
38 | for (String startupCommand : getStartupCommands()) {
39 | interpreter.eval(startupCommand);
40 | }
41 |
42 | interpreter.setExitOnEOF(false);
43 | interpreter.run();
44 | } catch (Exception e) {
45 | logError("Error: ", e);
46 | } finally {
47 | close();
48 | }
49 | }
50 |
51 | @Override
52 | public void onExit() {
53 | try {
54 | mConsole.close();
55 | } catch (IOException e) {
56 | // m'eh
57 | }
58 | close();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/ShowCreateTableCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import android.database.Cursor;
4 | import android.database.sqlite.SQLiteDatabase;
5 |
6 | import java.io.PrintWriter;
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 | import jwf.debugport.annotations.Command;
11 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
12 |
13 | /**
14 | * Print the CREATE TABLE syntax for a table.
15 | */
16 | @Command.Help(
17 | format = "SHOW CREATE TABLE [table name];",
18 | value = "Show the CREATE TABLE command used to create [table name].",
19 | group = Command.GROUP_SQL_INSPECTION
20 | )
21 | public class ShowCreateTableCommand extends SQLiteCommand {
22 | private static final Pattern command = Pattern.compile("(show\\s+create\\s+table|schema)\\s+(\\w+)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
23 |
24 | @Override
25 | public boolean isCommandString(String candidate) {
26 | return command.matcher(candidate).matches();
27 | }
28 |
29 | @Override
30 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
31 | Matcher m = command.matcher(commandString);
32 | if (!m.matches()) {
33 | return;
34 | }
35 |
36 | String tableName = m.group(2);
37 | if (!isValidTableName(tableName)) {
38 | out.println("Invalid table name `"+tableName+"`");
39 | } else {
40 | SQLiteDatabase db = connection.getCurrentDatabase();
41 | Cursor c = db.rawQuery("SELECT sql FROM sqlite_master WHERE name = ?", new String[]{tableName});
42 | if (c.getCount() == 0) {
43 | out.println("Table `"+tableName+"` does not exist.");
44 | } else {
45 | c.moveToNext();
46 | out.print(c.getString(0));
47 | out.println(":");
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/DropDatabaseCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import android.app.Application;
4 |
5 | import java.io.PrintWriter;
6 | import java.util.regex.Matcher;
7 | import java.util.regex.Pattern;
8 |
9 | import jwf.debugport.annotations.Command;
10 | import jwf.debugport.internal.Utils;
11 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
12 |
13 | /**
14 | * Created by jason on 5/9/16.
15 | */
16 | @Command.Help(
17 | format = "DROP DATABASE [database name];",
18 | value = "Drop the database named [database name] from the app's collection of databases.",
19 | group = Command.GROUP_SQL_DATABASES
20 | )
21 | public class DropDatabaseCommand extends SQLiteCommand {
22 | private static final Pattern command = Pattern.compile("\\s*drop\\s+database\\s+(\\w+)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
23 |
24 | @Override
25 | public boolean isCommandString(String candidate) {
26 | return command.matcher(candidate).matches();
27 | }
28 |
29 | @Override
30 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
31 | Matcher commandMatcher = command.matcher(commandString);
32 | if (!commandMatcher.matches()) {
33 | return;
34 | }
35 | String dbName = commandMatcher.group(1);
36 |
37 | if (!isValidDatabaseName(dbName)) {
38 | out.println("Invalid database name: "+dbName);
39 | out.flush();
40 | } else {
41 | Application app = connection.getApp();
42 |
43 | String[] databases = app.databaseList();
44 | boolean exists = false;
45 | if (!connection.databaseExists(dbName)) {
46 | out.println("Database with name `"+dbName+"` does not exist");
47 | return;
48 | }
49 |
50 | long start = System.nanoTime();
51 | app.deleteDatabase(dbName);
52 | long elapsed = System.nanoTime() - start;
53 | out.println("Database `" + dbName + "` dropped in " + Utils.nanosToMillis(elapsed));
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/CreateDatabaseCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import android.app.Application;
4 |
5 | import java.io.PrintWriter;
6 | import java.util.regex.Matcher;
7 | import java.util.regex.Pattern;
8 |
9 | import jwf.debugport.annotations.Command;
10 | import jwf.debugport.internal.Utils;
11 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
12 | import jwf.debugport.internal.sqlite.SQLiteOpenHelper;
13 |
14 | /**
15 | * Created by jason on 5/9/16.
16 | */
17 | @Command.Help(
18 | format = "CREATE DATABASE [database name];",
19 | value = "Create a new database called [database name].",
20 | group = Command.GROUP_SQL_DATABASES
21 | )
22 | public class CreateDatabaseCommand extends SQLiteCommand {
23 | private static final Pattern command = Pattern.compile("\\s*create\\s+database\\s+(\\w+)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
24 |
25 | @Override
26 | public boolean isCommandString(String candidate) {
27 | return command.matcher(candidate).matches();
28 | }
29 |
30 | @Override
31 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
32 | Matcher commandMatcher = command.matcher(commandString);
33 | if (!commandMatcher.matches()) {
34 | return;
35 | }
36 | String dbName = commandMatcher.group(1);
37 |
38 | if (!isValidDatabaseName(dbName)) {
39 | out.println("Invalid database name: "+dbName);
40 | out.flush();
41 | } else {
42 | Application app = connection.getApp();
43 |
44 | if (connection.databaseExists(dbName)) {
45 | out.println("Database `" + dbName + "` already exists");
46 | return;
47 | }
48 |
49 | long start = System.nanoTime();
50 | SQLiteOpenHelper helper = new SQLiteOpenHelper(connection, dbName);
51 | helper.open();
52 | long elapsed = System.nanoTime() - start;
53 | helper.close();
54 | out.println("Database `" + dbName + "` created in " + Utils.nanosToMillis(elapsed));
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/help.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import android.util.Log;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Collections;
7 |
8 | import bsh.CallStack;
9 | import bsh.Interpreter;
10 | import jwf.debugport.annotations.Command;
11 | import jwf.debugport.internal.CommandHelpInfo;
12 | import jwf.debugport.internal.Utils;
13 |
14 | /**
15 | *
16 | */
17 | @Command
18 | public class help {
19 | private static final ArrayList sCommandHelp = new ArrayList<>();
20 | private static final int MAX_WIDTH = 100;
21 | private static final int NAME_START_COL = 6;
22 | private static final int HELP_START_COL = 10;
23 | private static final int GROUP_START_COL = 2;
24 |
25 | @Command.Help("Show this help message.")
26 | public static void invoke(Interpreter interpreter, CallStack callStack) {
27 | try {
28 | StringBuilder builder = new StringBuilder();
29 | builder.append("Available Commands:");
30 | String lastCommandGroup = "__not_a_group__";
31 | for (CommandHelpInfo info : sCommandHelp) {
32 | builder.append("\n");
33 | String commandGroup = info.getCommandGroup();
34 | if (!commandGroup.equalsIgnoreCase(lastCommandGroup)) {
35 | if (!lastCommandGroup.equals("__not_a_group__")) {
36 | builder.append("\n");
37 | }
38 | builder.append(Utils.spaces(GROUP_START_COL))
39 | .append(commandGroup)
40 | .append(":")
41 | .append("\n");
42 | lastCommandGroup = commandGroup;
43 | }
44 | info.appendHelpInfoForMethods(builder, NAME_START_COL, HELP_START_COL, MAX_WIDTH);
45 | }
46 | builder.append("\n");
47 |
48 | interpreter.println(builder.toString());
49 | } catch (Exception e) {
50 | Log.e("OOPS", "hmm", e);
51 | }
52 | }
53 |
54 | public static void registerCommand(Class command) {
55 | if (!command.isAnnotationPresent(Command.class)) {
56 | return;
57 | }
58 | sCommandHelp.add(new CommandHelpInfo(command));
59 | Collections.sort(sCommandHelp);
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/ClientConnection.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.util.Log;
6 |
7 | import java.io.IOException;
8 | import java.lang.ref.WeakReference;
9 | import java.net.Socket;
10 |
11 | /**
12 | * Base ClientConnection type.
13 | */
14 | public abstract class ClientConnection implements Runnable {
15 | private static final String TAG = "ClientConnection";
16 | private final Application mApp;
17 | private final Socket mSocket;
18 | private final WeakReference mParent;
19 | private final String[] mStartupCommands;
20 | private String mLogTag;
21 |
22 | public ClientConnection(Context context, Socket client, TelnetServer parent, String[] startupCommands) {
23 | mApp = (Application) context.getApplicationContext();
24 | mSocket = client;
25 | mParent = new WeakReference(parent);
26 | mStartupCommands = startupCommands;
27 | }
28 |
29 | public Application getApp() {
30 | return mApp;
31 | }
32 |
33 | public Socket getSocket() {
34 | return mSocket;
35 | }
36 |
37 | public TelnetServer getParent() {
38 | return mParent.get();
39 | }
40 |
41 | public String[] getStartupCommands() {
42 | return mStartupCommands;
43 | }
44 |
45 | public void close() {
46 | logInfo("Client closing:" + getSocket().toString());
47 | TelnetServer parent = getParent();
48 | if (parent != null) {
49 | parent.notifyClosing(this);
50 | }
51 | try {
52 | closeConnection();
53 | getSocket().close();
54 | } catch (IOException e) {
55 | // m'eh..
56 | logError("Error upon closing.", e);
57 | }
58 | }
59 |
60 | protected void logInfo(String msg) {
61 | Log.i(getTag(), msg);
62 | }
63 |
64 | protected void logDebug(String msg) {
65 | Log.d(getTag(), msg);
66 | }
67 |
68 | protected void logError(String msg) {
69 | Log.e(getTag(), msg);
70 | }
71 |
72 | protected void logError(String msg, Throwable t) {
73 | Log.e(getTag(), msg, t);
74 | }
75 |
76 | private String getTag() {
77 | if (mLogTag == null) {
78 | mLogTag = getClass().getSimpleName();
79 | }
80 | return mLogTag;
81 | }
82 |
83 | public abstract void closeConnection();
84 | }
85 |
--------------------------------------------------------------------------------
/lib-noop/src/main/java/jwf/debugport/Params.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * Duplicate of Params class for no-op production usage.
8 | */
9 | public class Params implements Parcelable {
10 | public static final int DEFAULT_ANDROID_PORT = -1;
11 | public static final int DEFAULT_SQLITE_PORT = -1;
12 | private int mDebugPort;
13 | private int mSQLitePort;
14 | private String[] mStartupCommands;
15 |
16 | public Params() {
17 | mDebugPort = DEFAULT_ANDROID_PORT;
18 | mSQLitePort = DEFAULT_SQLITE_PORT;
19 | mStartupCommands = new String[0];
20 | }
21 |
22 | @Deprecated
23 | public Params setPort(int port) {
24 | return this;
25 | }
26 |
27 | @Deprecated
28 | public int getPort() {
29 | return mDebugPort;
30 | }
31 |
32 | public Params setDebugPort(int port) {
33 | return this;
34 | }
35 |
36 | public int getDebugPort() {
37 | return mDebugPort;
38 | }
39 |
40 | public Params setSQLitePort(int port) {
41 | return this;
42 | }
43 |
44 | public int getSQLitePort() {
45 | return mSQLitePort;
46 | }
47 |
48 | public Params setStartupCommands(String[] commands) {
49 | return this;
50 | }
51 |
52 | public String[] getStartupCommands() {
53 | return mStartupCommands;
54 | }
55 |
56 | @Override
57 | public int describeContents() {
58 | return hashCode();
59 | }
60 |
61 | @Override
62 | public void writeToParcel(Parcel dest, int flags) {
63 | dest.writeInt(mDebugPort);
64 | dest.writeInt(mSQLitePort);
65 | dest.writeInt(mStartupCommands.length);
66 | dest.writeStringArray(mStartupCommands);
67 | }
68 |
69 | @Override
70 | public String toString() {
71 | return "No-Op Params<>";
72 | }
73 |
74 | public static final Creator CREATOR = new Creator() {
75 | @Override
76 | public Params createFromParcel(Parcel source) {
77 | Params p = new Params();
78 | p.setDebugPort(source.readInt());
79 | p.setSQLitePort(source.readInt());
80 |
81 | int startupCommandsLength = source.readInt();
82 | String[] startupCommands = new String[startupCommandsLength];
83 | source.readStringArray(startupCommands);
84 | p.setStartupCommands(startupCommands);
85 | return p;
86 | }
87 |
88 | @Override
89 | public Params[] newArray(int size) {
90 | return new Params[size];
91 | }
92 | };
93 | }
94 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/jwf/debugport/app/MainActivity.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.app;
2 |
3 | import android.content.Context;
4 | import android.net.wifi.WifiInfo;
5 | import android.net.wifi.WifiManager;
6 | import android.os.Bundle;
7 | import android.support.annotation.Nullable;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.view.View;
10 | import android.widget.CompoundButton;
11 | import android.widget.TextView;
12 | import android.widget.ToggleButton;
13 |
14 | import java.util.Locale;
15 |
16 | import jwf.debugport.DebugPortService;
17 | import jwf.debugport.Params;
18 |
19 | /**
20 | *
21 | */
22 | public class MainActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {
23 | @Override
24 | protected void onCreate(@Nullable Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 |
27 | setContentView(R.layout.main_activity);
28 |
29 | ToggleButton toggle = (ToggleButton) findViewById(R.id.server_toggle);
30 | if (toggle != null) {
31 | toggle.setOnCheckedChangeListener(this);
32 | }
33 | }
34 |
35 | @Override
36 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
37 | TextView debugStatus = (TextView) findViewById(R.id.debug_server_status);
38 | TextView sqliteStatus = (TextView) findViewById(R.id.sqlite_server_status);
39 | if (isChecked) {
40 | Params params = DebugPortService.start(this);
41 | if (debugStatus != null) {
42 | debugStatus.setText(getString(R.string.debug_server_status, getIpAddress(), params.getDebugPort()));
43 | debugStatus.setVisibility(View.VISIBLE);
44 | }
45 | if (sqliteStatus != null) {
46 | sqliteStatus.setText(getString(R.string.sqlite_server_status, getIpAddress(), params.getSQLitePort()));
47 | sqliteStatus.setVisibility(View.VISIBLE);
48 | }
49 | } else {
50 | DebugPortService.stop(this);
51 | if (debugStatus != null) {
52 | debugStatus.setVisibility(View.GONE);
53 | }
54 | if (sqliteStatus != null) {
55 | sqliteStatus.setVisibility(View.GONE);
56 | }
57 | }
58 | }
59 |
60 | public String getIpAddress() {
61 | WifiManager wifiMan = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
62 | WifiInfo wifiInf = wifiMan.getConnectionInfo();
63 | int ipAddress = wifiInf.getIpAddress();
64 | return String.format(Locale.getDefault(), "%d.%d.%d.%d", (ipAddress & 0xff),(ipAddress >> 8 & 0xff),(ipAddress >> 16 & 0xff),(ipAddress >> 24 & 0xff));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/descriptors/MethodDescriptor.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands.descriptors;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.lang.reflect.Method;
6 | import java.lang.reflect.Type;
7 | import java.util.Arrays;
8 |
9 | /**
10 | *
11 | */
12 | public class MethodDescriptor extends MemberDescriptor {
13 | private final Method mMethod;
14 |
15 | public MethodDescriptor(Method method) {
16 | super(method);
17 | mMethod = method;
18 | }
19 |
20 | @Override
21 | public String toString() {
22 | StringBuilder sb = new StringBuilder();
23 |
24 | String modifiers = getModifierString();
25 | if (!TextUtils.isEmpty(modifiers)) {
26 | sb.append(modifiers);
27 | sb.append(" ");
28 | }
29 |
30 | try {
31 | Type genericReturnType = mMethod.getGenericReturnType();
32 | sb.append(getSimpleClassName(genericReturnType));
33 | sb.append(" ");
34 | } catch (Exception e) {
35 | Class> returnType = mMethod.getReturnType();
36 | if (returnType.equals(Void.TYPE)) {
37 | sb.append("void ");
38 | } else {
39 | sb.append(returnType.getSimpleName());
40 | sb.append(" ");
41 | }
42 | }
43 |
44 | sb.append(getName());
45 | sb.append("(");
46 | try {
47 | Class>[] params = mMethod.getParameterTypes();
48 | Type[] genericParams = mMethod.getGenericParameterTypes();
49 | for (int i = 0; i < genericParams.length; i++) {
50 | if (i != 0) {
51 | sb.append(", ");
52 | }
53 | Type paramType = genericParams[i];
54 |
55 | String name = getSimpleClassName(paramType);
56 | if (i == genericParams.length-1 && mMethod.isVarArgs() && params[i].isArray()) {
57 | sb.append(name.replace("[]", ""));
58 | sb.append("...");
59 | } else {
60 | sb.append(name);
61 | }
62 | }
63 | } catch (Exception e) {
64 | Class>[] params = mMethod.getParameterTypes();
65 | for (int i = 0; i < params.length; i++) {
66 | if (i != 0) {
67 | sb.append(", ");
68 | }
69 | String name = params[i].getSimpleName();
70 | if (i == params.length-1 && mMethod.isVarArgs() && params[i].isArray()) {
71 | sb.append(name.replace("[]", ""));
72 | sb.append("...");
73 | } else {
74 | sb.append(name);
75 | }
76 | }
77 | }
78 | sb.append(")");
79 |
80 | return sb.toString();
81 | }
82 |
83 | public static MethodDescriptor[] fromMethods(Method[] methods) {
84 | MethodDescriptor[] result = new MethodDescriptor[methods.length];
85 | for (int i = 0; i < result.length; i++) {
86 | result[i] = new MethodDescriptor(methods[i]);
87 | }
88 | Arrays.sort(result);
89 | return result;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/commands/HelpCommand.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite.commands;
2 |
3 | import java.io.PrintWriter;
4 | import java.util.ArrayList;
5 | import java.util.Collections;
6 | import java.util.regex.Pattern;
7 |
8 | import jwf.debugport.annotations.Command;
9 | import jwf.debugport.internal.CommandHelpInfo;
10 | import jwf.debugport.internal.Utils;
11 | import jwf.debugport.internal.sqlite.SQLiteClientConnection;
12 |
13 | /**
14 | * Created by jason on 5/15/16.
15 | */
16 | @Command.Help(
17 | value = "Show this help message.",
18 | format = "help;"
19 | )
20 | public class HelpCommand extends SQLiteCommand {
21 | private static final Pattern command = Pattern.compile("^help$", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
22 | private static final int MAX_WIDTH = 100;
23 | private static final int PREAMBLE_START_COL = 2;
24 | private static final int NAME_START_COL = 8;
25 | private static final int HELP_START_COL = 12;
26 | private static final int GROUP_START_COL = 4;
27 |
28 | @Override
29 | public boolean isCommandString(String candidate) {
30 | return command.matcher(candidate).matches();
31 | }
32 |
33 | @Override
34 | public void execute(SQLiteClientConnection connection, PrintWriter out, String commandString) {
35 | SQLiteCommand[] commands = Commands.getInstance().getCommands();
36 | ArrayList helpInfo = new ArrayList<>();
37 | for (SQLiteCommand cmd : commands) {
38 | if (cmd.getClass().isAnnotationPresent(Command.Help.class)) {
39 | helpInfo.add(new CommandHelpInfo(cmd.getClass()));
40 | }
41 | }
42 | Collections.sort(helpInfo);
43 |
44 | StringBuilder builder = new StringBuilder();
45 | builder.append("Help:\n");
46 | CommandHelpInfo.appendHelpText(
47 | builder,
48 | "As you'd expect, you can execute any valid SQLite statements against the database " +
49 | "to which you're currently connected (see: `USE [database name];` below).",
50 | PREAMBLE_START_COL,
51 | MAX_WIDTH);
52 | builder.append("\n\n");
53 | CommandHelpInfo.appendHelpText(
54 | builder,
55 | "In addition to regular SQLite commands, Android DebugPort provides additional " +
56 | "functionality via several additional commands.",
57 | PREAMBLE_START_COL,
58 | MAX_WIDTH);
59 | builder.append("\n\n");
60 | CommandHelpInfo.appendHelpText(
61 | builder,
62 | "Available non-SQLite commands (case insensitive):",
63 | PREAMBLE_START_COL,
64 | MAX_WIDTH);
65 | String lastCommandGroup = "__not_a_group__";
66 | for (CommandHelpInfo info : helpInfo) {
67 | builder.append("\n");
68 | String commandGroup = info.getCommandGroup();
69 | if (!commandGroup.equalsIgnoreCase(lastCommandGroup)) {
70 | if (!lastCommandGroup.equals("__not_a_group__")) {
71 | builder.append("\n");
72 | }
73 | builder.append(Utils.spaces(GROUP_START_COL))
74 | .append(commandGroup)
75 | .append(":")
76 | .append("\n");
77 | lastCommandGroup = commandGroup;
78 | }
79 | info.appendHelpInfoForClass(builder, NAME_START_COL, HELP_START_COL, MAX_WIDTH);
80 | }
81 | builder.append("\n");
82 |
83 | out.print(builder.toString());
84 | out.flush();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/TelnetServer.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | import java.io.IOException;
7 | import java.io.PrintWriter;
8 | import java.net.ServerSocket;
9 | import java.net.Socket;
10 | import java.net.UnknownHostException;
11 | import java.util.HashSet;
12 |
13 | import jwf.debugport.BuildConfig;
14 | import jwf.debugport.Params;
15 |
16 |
17 | /**
18 | *
19 | */
20 | public abstract class TelnetServer implements Runnable {
21 | private static final String TAG = "TelnetServer";
22 | private final Object mLock = new Object();
23 | private final Context mApp;
24 | private final Params mParams;
25 | private final int mPort;
26 | private ServerSocket mServerSocket;
27 | private Thread mThread;
28 | private volatile boolean mAlive = false;
29 | private HashSet mClients = new HashSet<>();
30 |
31 | public TelnetServer(Context app, Params params, int port) throws UnknownHostException {
32 | mApp = app;
33 | mParams = params;
34 | mPort = port;
35 | }
36 |
37 | public void startServer() throws IOException {
38 | synchronized (mLock) {
39 | if (mServerSocket != null && mServerSocket.isBound()) {
40 | throw new IOException("server already started");
41 | }
42 | }
43 | mThread = new Thread(this);
44 | mThread.start();
45 | }
46 |
47 | public void killServer() {
48 | if (mThread != null) {
49 | mThread.interrupt();
50 | }
51 | mAlive = false;
52 | synchronized (mLock) {
53 | for (ClientConnection client : mClients) {
54 | client.close();
55 | }
56 | try {
57 | mServerSocket.close();
58 | } catch (IOException e) {
59 | // m'eh
60 | }
61 | mServerSocket = null;
62 | }
63 | }
64 |
65 | @Override
66 | public void run() {
67 | try {
68 | mServerSocket = new ServerSocket(mPort);
69 | } catch (IOException e) {
70 | throw new RuntimeException(e);
71 | }
72 | mAlive = true;
73 | Log.i(getTag(), "Server running at "+ Utils.getIpAddress(mApp)+":"+mPort);
74 | while (mAlive) {
75 | Socket client;
76 | T clientConn;
77 | if (mServerSocket == null) {
78 | break;
79 | }
80 | try {
81 | client = mServerSocket.accept();
82 | PrintWriter out = new PrintWriter(client.getOutputStream());
83 | out.println();
84 | out.println("Android DebugPort v" + BuildConfig.VERSION_NAME);
85 | out.println("Report issues at https://github.com/jasonwyatt/Android-DebugPort/issues");
86 | out.println();
87 | out.flush();
88 | } catch (IOException e) {
89 | if (e.getMessage().equals("Socket closed")) {
90 | // no big deal, we are done here..
91 | break;
92 | }
93 | Log.w(getTag(), "An error occurred accepting a client connection.", e);
94 | continue;
95 | }
96 |
97 | synchronized (mLock) {
98 | clientConn = getClientConnection(mApp, client, this, mParams.getStartupCommands());
99 | mClients.add(clientConn);
100 | }
101 | new Thread(clientConn).start();
102 | }
103 | Log.i(getTag(), "Shutdown.");
104 | }
105 |
106 | public abstract T getClientConnection(Context c, Socket socket, TelnetServer server, String[] startupCommands);
107 |
108 | public void notifyClosing(T clientConnection) {
109 | if (!mAlive) {
110 | // we are dead anyway, no need to worry..
111 | return;
112 | }
113 |
114 | synchronized (mLock) {
115 | mClients.remove(clientConnection);
116 | }
117 | }
118 |
119 | private String getTag() {
120 | return getClass().getSimpleName();
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/Params.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 | import android.support.annotation.Nullable;
6 |
7 | /**
8 | * Configuration parameters for the {@link DebugPortService}
9 | */
10 | public class Params implements Parcelable {
11 | public static final int DEFAULT_ANDROID_PORT = 8562;
12 | public static final int DEFAULT_SQLITE_PORT = 8563;
13 | private int mDebugPort;
14 | private int mSQLitePort;
15 | private String[] mStartupCommands;
16 |
17 | public Params() {
18 | mDebugPort = DEFAULT_ANDROID_PORT;
19 | mSQLitePort = DEFAULT_SQLITE_PORT;
20 | mStartupCommands = new String[0];
21 | }
22 |
23 | /**
24 | * Set the port on which the {@link jwf.debugport.internal.TelnetServer} should be made available.
25 | * @deprecated Use {@link #setDebugPort(int)} instead.
26 | */
27 | @Deprecated
28 | public Params setPort(int port) {
29 | mDebugPort = port;
30 | return this;
31 | }
32 |
33 | /**
34 | * Get the port on which the {@link jwf.debugport.internal.TelnetServer} will be made available.
35 | * @deprecated Use {@link #getDebugPort()} instead.
36 | */
37 | @Deprecated
38 | public int getPort() {
39 | return mDebugPort;
40 | }
41 |
42 | /**
43 | * Set the port on which the debug port server will be made available.
44 | */
45 | public Params setDebugPort(int port) {
46 | mDebugPort = port;
47 | return this;
48 | }
49 |
50 | /**
51 | * Get the port on which the debug port server will be made available.
52 | */
53 | public int getDebugPort() {
54 | return mDebugPort;
55 | }
56 |
57 | /**
58 | * Set the port on which the SQLite-context server will be made available.
59 | */
60 | public Params setSQLitePort(int port) {
61 | mSQLitePort = port;
62 | return this;
63 | }
64 |
65 | /**
66 | * Get the port on which the SQLite-context server will be made available.
67 | */
68 | public int getSQLitePort() {
69 | return mSQLitePort;
70 | }
71 |
72 | /**
73 | * Set an array of commands which should be executed on the interpreter before the telnet client
74 | * is given control. This can be useful if you need to run a bunch of import
75 | * statements, or configure some state.
76 | */
77 | public Params setStartupCommands(@Nullable String[] commands) {
78 | if (commands == null) {
79 | commands = new String[0];
80 | }
81 | mStartupCommands = commands;
82 | return this;
83 | }
84 |
85 | /**
86 | * Get the startup commands which will be executed on the interpreter before each telnet client
87 | * is given control.
88 | */
89 | public String[] getStartupCommands() {
90 | return mStartupCommands;
91 | }
92 |
93 | @Override
94 | public int describeContents() {
95 | return hashCode();
96 | }
97 |
98 | @Override
99 | public void writeToParcel(Parcel dest, int flags) {
100 | dest.writeInt(mDebugPort);
101 | dest.writeInt(mSQLitePort);
102 | dest.writeInt(mStartupCommands.length);
103 | dest.writeStringArray(mStartupCommands);
104 | }
105 |
106 | @Override
107 | public String toString() {
108 | return "Params";
109 | }
110 |
111 | public static final Creator CREATOR = new Creator() {
112 | @Override
113 | public Params createFromParcel(Parcel source) {
114 | Params p = new Params();
115 | p.setDebugPort(source.readInt());
116 | p.setSQLitePort(source.readInt());
117 |
118 | int startupCommandsLength = source.readInt();
119 | String[] startupCommands = new String[startupCommandsLength];
120 | source.readStringArray(startupCommands);
121 | p.setStartupCommands(startupCommands);
122 | return p;
123 | }
124 |
125 | @Override
126 | public Params[] newArray(int size) {
127 | return new Params[size];
128 | }
129 | };
130 | }
131 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/sqlite/SQLiteClientConnection.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.sqlite;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 | import android.database.sqlite.SQLiteException;
7 |
8 | import java.io.PrintWriter;
9 | import java.net.Socket;
10 | import java.util.Scanner;
11 |
12 | import jwf.debugport.internal.ClientConnection;
13 | import jwf.debugport.internal.TelnetServer;
14 | import jwf.debugport.internal.Utils;
15 | import jwf.debugport.internal.sqlite.commands.Commands;
16 | import jwf.debugport.internal.sqlite.commands.SQLiteCommand;
17 |
18 | /**
19 | * ClientConnection implementation for an SQLite REPL with MySQL-style params.
20 | */
21 | public class SQLiteClientConnection extends ClientConnection {
22 | private SQLiteOpenHelper mCurrentDb;
23 |
24 | public SQLiteClientConnection(Context context, Socket client, TelnetServer parent, String[] startupCommands) {
25 | super(context, client, parent, startupCommands);
26 | }
27 |
28 | @Override
29 | public void closeConnection() {
30 | if (mCurrentDb != null) {
31 | mCurrentDb.close();
32 | }
33 | }
34 |
35 | @Override
36 | public void run() {
37 | try {
38 | Socket s = getSocket();
39 | Scanner in = new Scanner(s.getInputStream());
40 | in.useDelimiter(";");
41 | PrintWriter out = new PrintWriter(s.getOutputStream(), true);
42 |
43 | out.println("SQLite Database REPL");
44 | boolean wasExitCommand = false;
45 | do {
46 | out.println();
47 | out.print("sqlite> ");
48 | out.flush();
49 |
50 | String commandCandidate = in.next().trim();
51 | SQLiteCommand command = Commands.getInstance().getCommand(commandCandidate);
52 | if (command != null) {
53 | command.execute(this, out, commandCandidate);
54 | wasExitCommand = command.isExitCommand();
55 | } else {
56 | if (mCurrentDb != null) {
57 | SQLiteDatabase db = mCurrentDb.open();
58 | long start = System.nanoTime();
59 | try {
60 | Cursor c = db.rawQuery(commandCandidate, null);
61 | int rows = c.getCount();
62 | long elapsed = System.nanoTime() - start;
63 | Utils.printTable(out, c);
64 |
65 | if (rows > 0) {
66 | out.println(rows + " rows in set (" + Utils.nanosToMillis(elapsed) + ")");
67 | } else {
68 | out.println("Query executed in " + Utils.nanosToMillis(elapsed) + ".");
69 | }
70 | } catch (SQLiteException sqle) {
71 | out.println(sqle.getMessage());
72 | }
73 | } else {
74 | printNoDBSelected(out);
75 | }
76 | }
77 | } while (!wasExitCommand);
78 | } catch (Exception e) {
79 | logError("Error:", e);
80 | } finally {
81 | close();
82 | }
83 | }
84 |
85 | public void printNoDBSelected(PrintWriter out) {
86 | out.println("No database currently selected. Call `use [table name];` to select one, or `show databases;` to list available databases.");
87 | }
88 |
89 | public void setCurrentDatabase(String dbName) {
90 | if (mCurrentDb != null) {
91 | mCurrentDb.close();
92 | }
93 | mCurrentDb = new SQLiteOpenHelper(this, dbName);
94 | mCurrentDb.open();
95 | }
96 |
97 | public SQLiteDatabase getCurrentDatabase() {
98 | if (mCurrentDb == null) {
99 | return null;
100 | }
101 | return mCurrentDb.open();
102 | }
103 |
104 | public boolean databaseExists(String dbName) {
105 | String[] databases = getApp().databaseList();
106 | boolean exists = false;
107 | for (String existingDb : databases) {
108 | if (existingDb.equals(dbName)) {
109 | return true;
110 | }
111 | }
112 | return false;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/call.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands;
2 |
3 | import java.lang.reflect.InvocationTargetException;
4 | import java.lang.reflect.Method;
5 |
6 | import bsh.CallStack;
7 | import bsh.Interpreter;
8 | import jwf.debugport.annotations.Command;
9 |
10 | /**
11 | *
12 | */
13 | @Command(group = Command.GROUP_ACCESS)
14 | public class call {
15 | /*
16 | * In order to support a varargs-like behavior when the command is called with no third
17 | * parameter, we have to supply several methods.. beanshell doesn't really support varargs well.
18 | */
19 | public static Object invoke(
20 | Interpreter interpreter,
21 | CallStack callStack,
22 | Object object,
23 | String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
24 | return invokeInner(object, methodName, null);
25 | }
26 |
27 | public static Object invoke(
28 | Interpreter interpreter,
29 | CallStack callStack,
30 | Object object,
31 | String methodName,
32 | Object p1) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
33 | return invokeInner(object, methodName, p1);
34 | }
35 |
36 | public static Object invoke(
37 | Interpreter interpreter,
38 | CallStack callStack,
39 | Object object,
40 | String methodName,
41 | Object p1,
42 | Object p2) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
43 | return invokeInner(object, methodName, p1, p2);
44 | }
45 |
46 | public static Object invoke(
47 | Interpreter interpreter,
48 | CallStack callStack,
49 | Object object,
50 | String methodName,
51 | Object p1,
52 | Object p2,
53 | Object p3) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
54 | return invokeInner(object, methodName, p1, p2, p3);
55 | }
56 |
57 | public static Object invoke(
58 | Interpreter interpreter,
59 | CallStack callStack,
60 | Object object,
61 | String methodName,
62 | Object p1,
63 | Object p2,
64 | Object p3,
65 | Object p4) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
66 | return invokeInner(object, methodName, p1, p2, p3, p4);
67 | }
68 |
69 | public static Object invoke(
70 | Interpreter interpreter,
71 | CallStack callStack,
72 | Object object,
73 | String methodName,
74 | Object p1,
75 | Object p2,
76 | Object p3,
77 | Object p4,
78 | Object p5) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
79 | return invokeInner(object, methodName, p1, p2, p3, p4, p5);
80 | }
81 |
82 | public static Object invoke(
83 | Interpreter interpreter,
84 | CallStack callStack,
85 | Object object,
86 | String methodName,
87 | Object p1,
88 | Object p2,
89 | Object p3,
90 | Object p4,
91 | Object p5,
92 | Object p6) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
93 | return invokeInner(object, methodName, p1, p2, p3, p4, p5, p6);
94 | }
95 |
96 | @Command.Help("Call a method, regardless of access modifiers, on the provided object.")
97 | public static Object invoke(
98 | Interpreter interpreter,
99 | CallStack callStack,
100 | @Command.ParamName("obj") Object object,
101 | @Command.ParamName("method") String methodName,
102 | @Command.ParamName("params") Object... params) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
103 | return invokeInner(object, methodName, params);
104 | }
105 |
106 | private static Object invokeInner(
107 | Object object,
108 | String methodName,
109 | Object... params) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
110 | Method method;
111 | Class>[] paramClasses = new Class>[0];
112 | if (params != null) {
113 | paramClasses = new Class>[params.length];
114 | for (int i = 0; i < params.length; i++) {
115 | paramClasses[i] = params[i].getClass();
116 | }
117 | }
118 |
119 | try {
120 | method = object.getClass().getDeclaredMethod(methodName, paramClasses);
121 | } catch (NoSuchMethodException e) {
122 | // try a method on a parent of the object's class
123 | method = object.getClass().getMethod(methodName, paramClasses);
124 | }
125 | method.setAccessible(true);
126 | return method.invoke(object, params);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/Utils.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.net.wifi.WifiInfo;
7 | import android.net.wifi.WifiManager;
8 |
9 | import java.io.PrintWriter;
10 | import java.util.Locale;
11 |
12 | /**
13 | *
14 | */
15 | public class Utils {
16 | public static final int INDENT_SPACES = 2;
17 |
18 | @SuppressLint("DefaultLocale")
19 | public static String getIpAddress(Context context) {
20 | WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
21 | WifiInfo wifiInf = wifiMan.getConnectionInfo();
22 | int ipAddress = wifiInf.getIpAddress();
23 | return String.format("%d.%d.%d.%d", (ipAddress & 0xff),(ipAddress >> 8 & 0xff),(ipAddress >> 16 & 0xff),(ipAddress >> 24 & 0xff));
24 | }
25 |
26 | public static void printTable(PrintWriter out, String[] headers, Object[][] data) {
27 | int[] longest = new int[headers.length];
28 | for (int column = 0; column < headers.length; column++) {
29 | longest[column] = headers[column].length();
30 | }
31 | for (int row = 0; row < data.length; row++) {
32 | for (int column = 0; column < data[row].length; column++) {
33 | String dataStr = data[row][column].toString();
34 | if (dataStr.length() > longest[column]) {
35 | longest[column] = dataStr.length();
36 | }
37 | }
38 | }
39 |
40 | int gutter = 1;
41 |
42 | printTableDivider(out, longest, gutter);
43 | printTableRow(out, longest, gutter, headers);
44 | printTableDivider(out, longest, gutter);
45 | for (int row = 0; row < data.length; row++) {
46 | printTableRow(out, longest, gutter, data[row]);
47 | }
48 | printTableDivider(out, longest, gutter);
49 | }
50 |
51 | private static void printTableRow(PrintWriter out, int[] columnWidths, int columnGutterSize, Object[] data) {
52 | out.print("|");
53 | for (int i = 0; i < data.length; i++) {
54 | out.print(multStr(" ", columnGutterSize));
55 | String dataStr = data[i].toString();
56 | out.print(dataStr);
57 | out.print(multStr(" ", columnGutterSize + (columnWidths[i] - dataStr.length())));
58 | out.print("|");
59 | }
60 | out.println();
61 | }
62 |
63 | private static void printTableDivider(PrintWriter out, int[] columnWidths, int columnGutterSize) {
64 | out.print("+");
65 | for (int i = 0; i < columnWidths.length; i++) {
66 | out.print(multStr("-", columnWidths[i] + columnGutterSize * 2));
67 | out.print("+");
68 | }
69 | out.println();
70 | }
71 |
72 | public static String multStr(String s, int times) {
73 | StringBuilder sb = new StringBuilder(s.length() * times);
74 | for (int i = 0; i < times; i++) {
75 | sb.append(s);
76 | }
77 | return sb.toString();
78 | }
79 |
80 | public static void printTable(PrintWriter out, Cursor c) {
81 | int columns = c.getColumnCount();
82 | if (columns == 0) {
83 | return;
84 | }
85 | String[] columnNames = c.getColumnNames();
86 | int gutter = 1;
87 | int[] longest = new int[columns];
88 | for (int column = 0; column < columns; column++) {
89 | longest[column] = columnNames[column].length();
90 | }
91 | int row = 0;
92 | while (c.moveToNext()) {
93 | for (int column = 0; column < columns; column++) {
94 | String dataStr = c.isNull(column) ? "null" : c.getString(column);
95 | if (dataStr.length() > longest[column]) {
96 | longest[column] = dataStr.length();
97 | }
98 | }
99 | }
100 |
101 | printTableDivider(out, longest, gutter);
102 | printTableRow(out, longest, gutter, columnNames);
103 | printTableDivider(out, longest, gutter);
104 |
105 | // let's rewind to actually print the contents.
106 | boolean hasFirst = c.moveToFirst();
107 |
108 |
109 | if (hasFirst) {
110 | do {
111 | String[] data = new String[columns];
112 | for (int i = 0; i < columns; i++) {
113 | data[i] = c.isNull(i) ? "NULL" : c.getString(i);
114 | }
115 | printTableRow(out, longest, gutter, data);
116 | } while (c.moveToNext());
117 | }
118 |
119 | printTableDivider(out, longest, gutter);
120 | }
121 |
122 | public static String nanosToMillis(long nanos) {
123 | float millis = nanos / 1000000.0f;
124 | return String.format(Locale.getDefault(), "%.3fms", millis);
125 | }
126 |
127 | public static String spaces(int spaces) {
128 | return multStr(" ", spaces);
129 | }
130 |
131 | public static String indent(int indentations) {
132 | return spaces(indentations * INDENT_SPACES);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/debug/commands/descriptors/MemberDescriptor.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal.debug.commands.descriptors;
2 |
3 | import java.lang.reflect.GenericArrayType;
4 | import java.lang.reflect.Member;
5 | import java.lang.reflect.Modifier;
6 | import java.lang.reflect.ParameterizedType;
7 | import java.lang.reflect.Type;
8 | import java.util.regex.Matcher;
9 | import java.util.regex.Pattern;
10 |
11 | /**
12 | *
13 | */
14 | public class MemberDescriptor implements Comparable {
15 | private final int mModifiers;
16 | private final Class> mClass;
17 | private final String mName;
18 |
19 | public MemberDescriptor(Member obj) {
20 | mModifiers = obj.getModifiers();
21 | mClass = obj.getDeclaringClass();
22 | mName = obj.getName();
23 | }
24 |
25 | public String getModifierString() {
26 | return Modifier.toString(mModifiers);
27 | }
28 |
29 | public String getName() {
30 | return mName;
31 | }
32 |
33 | public String getClassName() {
34 | return mClass.getSimpleName();
35 | }
36 |
37 | public int compareVisibility(MemberDescriptor another) {
38 | int visibilityMask = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED;
39 | int myVisibility = mModifiers & visibilityMask;
40 | int theirVisibility = another.mModifiers & visibilityMask;
41 |
42 | if (myVisibility < theirVisibility) {
43 | return -1;
44 | }
45 | if (myVisibility > theirVisibility) {
46 | return 1;
47 | }
48 |
49 | return 0;
50 | }
51 |
52 | @SuppressWarnings("NullableProblems")
53 | @Override
54 | public int compareTo(MemberDescriptor another) {
55 | if (another == null) {
56 | return -1;
57 | }
58 | // first sort by staticness...
59 | if (!Modifier.isStatic(mModifiers) && Modifier.isStatic(another.mModifiers)) {
60 | return -1;
61 | } else if (Modifier.isStatic(mModifiers) && !Modifier.isStatic(another.mModifiers)) {
62 | return 1;
63 | }
64 |
65 | // then by visibility
66 | int visibilityComparison = compareVisibility(another);
67 | if (visibilityComparison != 0) {
68 | return visibilityComparison;
69 | }
70 |
71 | // then by name
72 | return mName.compareTo(another.mName);
73 | }
74 |
75 | public static String getParametrizedTypeString(ParameterizedType type) {
76 | StringBuilder sb = new StringBuilder();
77 | Type[] typeArgs = type.getActualTypeArguments();
78 | sb.append("<");
79 | for (int i = 0; i < typeArgs.length; i++) {
80 | if (i != 0) {
81 | sb.append(", ");
82 | }
83 | sb.append(getSimpleClassName(typeArgs[i]));
84 | }
85 | sb.append(">");
86 | return sb.toString();
87 | }
88 |
89 | private static final Pattern sTypeSimpleClassPattern = Pattern.compile("([@a-z]+ )?([a-zA-Z0-9_]*\\.)*([a-zA-Z0-9_\\$]+)");
90 | private static final Pattern sTypeArraySimpleClassPattern = Pattern.compile("([@a-z]+ )?\\[L([a-zA-Z0-9_]*\\.)*([a-zA-Z0-9_\\$]+);?");
91 | public static String getSimpleClassName(Type type) {
92 | if (type instanceof ParameterizedType) {
93 | ParameterizedType pType = (ParameterizedType) type;
94 | StringBuilder sb = new StringBuilder();
95 | sb.append(getSimpleClassName(pType.getRawType()));
96 | Type[] args = pType.getActualTypeArguments();
97 | if (args.length > 0) {
98 | sb.append("<");
99 | for (int i = 0; i < args.length; i++) {
100 | if (i != 0) {
101 | sb.append(", ");
102 | }
103 | sb.append(getSimpleClassName(args[i]));
104 | }
105 | sb.append(">");
106 | }
107 | return sb.toString();
108 | }
109 | if (type instanceof GenericArrayType) {
110 | GenericArrayType gType = (GenericArrayType) type;
111 | return getSimpleClassName(gType.getGenericComponentType());
112 | }
113 |
114 | Matcher matcher = sTypeSimpleClassPattern.matcher(type.toString());
115 | if (matcher.matches()) {
116 | String result = matcher.group(3);
117 | return result.replaceAll("\\$", ".");
118 | }
119 | matcher = sTypeArraySimpleClassPattern.matcher(type.toString());
120 | if (matcher.matches()) {
121 | // must be an array..
122 | String result = matcher.group(3);
123 | return result.replaceAll("\\$", ".") + "[]";
124 | }
125 |
126 | // handle primitive others..
127 | if (type instanceof Class) {
128 | Class c = (Class) type;
129 | StringBuilder sb = new StringBuilder();
130 | sb.append(c.getComponentType().toString());
131 | if (c.isArray()) {
132 | sb.append("[]");
133 | }
134 | return sb.toString();
135 | }
136 | // fallback
137 | return type.toString();
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/internal/CommandHelpInfo.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport.internal;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.lang.annotation.Annotation;
6 | import java.lang.reflect.Method;
7 | import java.util.StringTokenizer;
8 |
9 | import jwf.debugport.annotations.Command;
10 |
11 | /**
12 | * Created by jason on 5/15/16.
13 | */
14 | public class CommandHelpInfo implements Comparable {
15 | private final Class mCommandClass;
16 | private Command.Help mHelpAnnotation;
17 | private Command mCommandAnnotation;
18 |
19 | public CommandHelpInfo(Class commandClass) {
20 | mCommandClass = commandClass;
21 | if (commandClass.isAnnotationPresent(Command.class)) {
22 | mCommandAnnotation = (Command) commandClass.getAnnotation(Command.class);
23 | }
24 | if (commandClass.isAnnotationPresent(Command.Help.class)) {
25 | mHelpAnnotation = (Command.Help) commandClass.getAnnotation(Command.Help.class);
26 | }
27 | }
28 |
29 | public String getCommandGroup() {
30 | if (mHelpAnnotation != null && !TextUtils.isEmpty(mHelpAnnotation.group())) {
31 | return mHelpAnnotation.group();
32 | }
33 | if (mCommandAnnotation != null && !TextUtils.isEmpty(mCommandAnnotation.group())) {
34 | return mCommandAnnotation.group();
35 | }
36 | return Command.GROUP_OTHER;
37 | }
38 |
39 | public void appendHelpInfoForMethods(StringBuilder target, int nameStartCol, int helpStartCol, int maxCols) {
40 | Method[] methods = mCommandClass.getMethods();
41 | boolean isFirst = true;
42 | for (Method method : methods) {
43 | if (!method.getName().equals("invoke") || !method.isAnnotationPresent(Command.Help.class)) {
44 | continue;
45 | }
46 |
47 | // separate each item with a new line.
48 | if (!isFirst) {
49 | target.append("\n");
50 | }
51 | isFirst = false;
52 |
53 | target.append(Utils.spaces(nameStartCol));
54 | String signature = getSignature(method);
55 | target.append(signature);
56 | target.append("\n");
57 |
58 | appendHelpText(target, method.getAnnotation(Command.Help.class).value(), helpStartCol, maxCols);
59 | }
60 | }
61 |
62 | public void appendHelpInfoForClass(StringBuilder target, int nameStartCol, int helpStartCol, int maxCols) {
63 | target.append(Utils.spaces(nameStartCol));
64 | if (!TextUtils.isEmpty(mHelpAnnotation.format())) {
65 | target.append(mHelpAnnotation.format());
66 | } else {
67 | target.append(mCommandClass.getSimpleName());
68 | }
69 | target.append("\n");
70 | appendHelpText(target, mHelpAnnotation.value(), helpStartCol, maxCols);
71 | }
72 |
73 | String getSignature(Method method) {
74 | Annotation[][] paramAnnotations = method.getParameterAnnotations();
75 | Class>[] params = method.getParameterTypes();
76 | StringBuilder signatureBuilder = new StringBuilder();
77 | signatureBuilder.append(mCommandClass.getSimpleName());
78 | signatureBuilder.append("(");
79 | for (int j = 2; j < params.length; j++) {
80 | // first two params are Interpreter and CallStack, ignore those.
81 | if (j != 2) {
82 | signatureBuilder.append(", ");
83 | }
84 |
85 | String name = params[j].getSimpleName();
86 | if (j == params.length - 1 && method.isVarArgs() && params[j].isArray()) {
87 | signatureBuilder.append(name.replace("[]", ""));
88 | signatureBuilder.append("...");
89 | } else {
90 | signatureBuilder.append(name);
91 | }
92 |
93 | Annotation[] annotations = paramAnnotations[j];
94 | Command.ParamName paramAnnotation = null;
95 | for (Annotation a : annotations) {
96 | if (a instanceof Command.ParamName) {
97 | paramAnnotation = (Command.ParamName) a;
98 | break;
99 | }
100 | }
101 | if (paramAnnotation != null) {
102 | if (!TextUtils.isEmpty(paramAnnotation.value())) {
103 | signatureBuilder.append(" ");
104 | signatureBuilder.append(paramAnnotation.value());
105 | }
106 | }
107 | }
108 | signatureBuilder.append(")");
109 | return signatureBuilder.toString();
110 | }
111 |
112 | public static void appendHelpText(StringBuilder target, String helpText, int helpStartCol, int maxCols) {
113 | int cols = helpStartCol;
114 | target.append(Utils.spaces(helpStartCol));
115 |
116 | StringTokenizer tokens = new StringTokenizer(helpText, " ");
117 | while (tokens.hasMoreTokens()) {
118 | String token = tokens.nextToken();
119 | target.append(token);
120 | if (tokens.hasMoreTokens()) {
121 | target.append(" ");
122 | }
123 | cols += token.length() + 1;
124 | if (cols >= maxCols && tokens.hasMoreTokens()) {
125 | cols = helpStartCol;
126 | target.append("\n");
127 | target.append(Utils.spaces(helpStartCol));
128 | }
129 | }
130 | }
131 |
132 | @SuppressWarnings("NullableProblems")
133 | @Override
134 | public int compareTo(CommandHelpInfo another) {
135 | if (another == null) {
136 | return -1;
137 | }
138 | String myGroup = getCommandGroup();
139 | String theirGroup = another.getCommandGroup();
140 | int groupCompare = myGroup.compareTo(theirGroup);
141 | if (groupCompare != 0) {
142 | // "Other" group should always come last.
143 | if (myGroup.equals(Command.GROUP_OTHER)) {
144 | return 1;
145 | }
146 | if (theirGroup.equals(Command.GROUP_OTHER)) {
147 | return - 1;
148 | }
149 | return groupCompare;
150 | }
151 | return mCommandClass.getSimpleName().compareTo(another.mCommandClass.getSimpleName());
152 | }
153 |
154 | public Class getCommandClass() {
155 | return mCommandClass;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://jitpack.io/#jasonwyatt/Android-DebugPort) [](https://android-arsenal.com/details/1/3540) [](http://www.apache.org/licenses/LICENSE-2.0)
2 |
3 | # Android DebugPort
4 |
5 | Android DebugPort is a drop-in utility which allows you to write and execute code within your app's context, at runtime, and from the comfort of your computer's terminal. Think of it as a window into your application through which you can both inspect _and_ modify its state.
6 |
7 | You can connect to one of two REPL servers running within your app:
8 |
9 | * Debug REPL - Run java-like code and inspect/modify the state of your android application.
10 | * SQLite REPL - Execute queries against your app's SQLite databaases.
11 |
12 | ## Getting Started - Drop-in
13 |
14 | ### Configure Your Dependencies
15 |
16 | Add the jitpack.io repository to your root `build.gradle`:
17 |
18 | ```groovy
19 | allprojects {
20 | repositories {
21 | jcenter()
22 | maven { url "https://jitpack.io" }
23 | }
24 | }
25 | ```
26 |
27 | In your application's `build.gradle` file, add a dependency for Android DebugPort:
28 |
29 | ```groovy
30 | debugCompile 'com.github.jasonwyatt.Android-DebugPort:lib:2.1.0'
31 | releaseCompile 'com.github.jasonwyatt.Android-DebugPort:lib-noop:2.1.0'
32 | ```
33 |
34 | **Note:** The final line above will use a [no-op version of the DebugPort library](https://github.com/jasonwyatt/Android-DebugPort-NOOP) in production builds. This makes it impossible for people to run the DebugPort server on a production build.
35 |
36 | ### Run Your App
37 |
38 | When you start your app after building for debug, you will see a low-priority notification in your system tray which will allow you to start the debugport servers.
39 |
40 | ### Connecting to the Debug Server
41 |
42 | $ telnet 192.168.2.83 8562 # on MacOS High Sierra: `nc 192.168.2.83 8562`
43 | Trying 192.168.2.83...
44 | Connected to 192.168.2.83.
45 | Escape character is '^]'.
46 |
47 | Android DebugPort v1.0
48 | Report issues at https://github.com/jasonwyatt/Android-DebugPort/issues
49 |
50 | BeanShell 2.0b6 - by Pat Niemeyer (pat@pat.net)
51 | bsh %
52 |
53 | There are a few built in commands, to see what they are, run `help();`
54 |
55 | bsh % help();
56 | Available Commands:
57 | Access:
58 | call(Object obj, String method, Object... params)
59 | Call a method, regardless of access modifiers, on the provided object.
60 | get(Object obj, String fieldName)
61 | Get the value of a field, regardless of access modifiers, on the provided object.
62 | set(Object obj, String fieldName, Object value)
63 | Set the value of a field on the provided object to the given value, regardless of access modifiers.
64 |
65 | Field Inspection:
66 | fields(Class class)
67 | List all of the fields available for a particular class.
68 | fields(Object obj)
69 | List all of the fields available for a particular object.
70 | fieldsLocal(Class class)
71 | List all of the fields defined locally for a particular class.
72 | fieldsLocal(Object obj)
73 | List all of the fields defined locally for an object.
74 |
75 | Method Inspection:
76 | methods(Class class)
77 | Get the available methods for the provided class.
78 | methods(Object obj)
79 | Get the available methods for the provided object.
80 | methodsLocal(Class class)
81 | Show all of the locally-declared methods for the provided class.
82 | methodsLocal(Object obj)
83 | Show all of the locally-declared methods for the provided object.
84 |
85 | Other:
86 | exit()
87 | Exit this interpreter.
88 | help()
89 | Show this help message.
90 | source(String scriptPath)
91 | Load and run a Beanshell script within your app's assets folder.
92 |
93 | bsh %
94 |
95 | Also, your application variable is automatically included as a global variable in the interpreter. It's called `app`. Try running `methodsLocal(app);`:
96 |
97 | bsh % methodsLocal(app);
98 | declared methods: {
99 | public void onCreate()
100 | }
101 | bsh %
102 |
103 | Don't forget that you can execute whatever code you wish within the DebugPort. See the [beanshell documentation](http://beanshell.org/manual/contents.html) for the full rundown.
104 |
105 | You can exit at any time by running the `exit();` command.
106 |
107 | ### Connecting to the SQLite Server
108 |
109 | $ telnet 192.168.0.100 8563 # on MacOS High Sierra: `nc 192.168.2.83 8563`
110 | Trying 192.168.0.100...
111 | Connected to 192.168.0.100.
112 | Escape character is '^]'.
113 |
114 | Android DebugPort v1.0
115 | Report issues at https://github.com/jasonwyatt/Android-DebugPort/issues
116 |
117 | SQLite Database REPL
118 |
119 | sqlite>
120 |
121 | As with the Debug server, there is a help command for the SQLite server:
122 |
123 | sqlite> help;
124 | Help:
125 | As you'd expect, you can execute any valid SQLite statements against the database to which you're
126 | currently connected (see: `USE [database name];` below).
127 |
128 | In addition to regular SQLite commands, Android DebugPort provides additional functionality via several
129 | additional commands.
130 |
131 | Available non-SQLite commands (case insensitive):
132 | Databases:
133 | CREATE DATABASE [database name];
134 | Create a new database called [database name].
135 | DROP DATABASE [database name];
136 | Drop the database named [database name] from the app's collection of databases.
137 | USE [database name];
138 | Connect to the database called [database name]. All SQL commands will be executed against
139 | this database until USE is called again.
140 |
141 | Inspection:
142 | SHOW CREATE TABLE [table name];
143 | Show the CREATE TABLE command used to create [table name].
144 | SHOW DATABASES;
145 | Show all available databases for the app, including temporary databases.
146 | SHOW TABLES;
147 | Show all of the tables defined for the database to which you are currently connected.
148 |
149 | Other:
150 | exit; or quit;
151 | Exit this interpreter.
152 | help;
153 | Show this help message.
154 |
155 | sqlite>
156 |
157 | Try running `show databases;` to see the available databases for your app:
158 |
159 | sqlite> show databases;
160 | +----------+
161 | | Database |
162 | +----------+
163 | | blog |
164 | | projects |
165 | +----------+
166 |
167 | sqlite>
168 |
169 | Run `use [database name];` to connect to a database, and once you're connected, you can run any SQLite command you want. You can quit at any time by running the `exit;` command.
170 |
171 | ## Advanced Configuration
172 |
173 | You can configure Android-DebugPort by setting any of the following `` values in your Application's `AndroidManifest.xml`.
174 |
175 | ```xml
176 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 | ```
191 |
192 | **Note:** It is recommended that if you wish to supply these meta-data values, you should consider setting them within an `AndroidManifest.xml` file for the `debug` build variant.
193 |
194 | ## License
195 | This library is released under the [Apache 2.0 License](https://github.com/jasonwyatt/Android-DebugPort/blob/master/LICENCE).
196 |
197 |
--------------------------------------------------------------------------------
/lib/src/main/java/jwf/debugport/DebugPortService.java:
--------------------------------------------------------------------------------
1 | package jwf.debugport;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.app.PendingIntent;
7 | import android.app.Service;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.pm.ApplicationInfo;
11 | import android.content.pm.PackageManager;
12 | import android.content.res.Resources;
13 | import android.os.AsyncTask;
14 | import android.os.Build;
15 | import android.os.Bundle;
16 | import android.os.IBinder;
17 | import android.os.PowerManager;
18 | import android.support.annotation.NonNull;
19 | import android.support.annotation.Nullable;
20 | import android.support.v4.app.NotificationCompat;
21 | import android.util.Log;
22 |
23 | import jwf.debugport.internal.debug.DebugTelnetServer;
24 | import jwf.debugport.internal.TelnetServer;
25 | import jwf.debugport.internal.Utils;
26 | import jwf.debugport.internal.sqlite.SQLiteTelnetServer;
27 |
28 | /**
29 | *
30 | */
31 | public class DebugPortService extends Service {
32 | private static final String TAG = "DebugPortService";
33 | private static final String NOTIFICATION_CHANNEL = "androiddebugport";
34 | public static final String METADATA_DEBUG_PORT = "jwf.debugport.METADATA_DEBUG_PORT";
35 | public static final String METADATA_SQLITE_PORT = "jwf.debugport.METADATA_SQLITE_PORT";
36 | public static final String METADATA_STARTUP_COMMANDS = "jwf.debugport.METADATA_STARTUP_COMMANDS";
37 | private static final String INTENT_EXTRA_PARAMS = "jwf.debugport.PARAMS";
38 | private static final String ACTION_KILL = "jwf.debugport.ACTION_KILL";
39 | private static final String ACTION_STOP = "jwf.debugport.ACTION_STOP";
40 | private static final String ACTION_START = "jwf.debugport.ACTION_START";
41 | private static final String ACTION_INITIALIZE = "jwf.debugport.ACTION_INIT";
42 | private static final int STOP_REQUEST_CODE = 0;
43 | private static final int START_REQUEST_CODE = 1;
44 | private static final int NOTIFICATION_ID = R.id.debugport_notification_id;
45 | private TelnetServer mDebugServer;
46 | private PowerManager.WakeLock mWakeLock;
47 | private SQLiteTelnetServer mSQLiteServer;
48 | private Params mParams;
49 | private boolean mServersStarted;
50 |
51 | /**
52 | * Utility method to start the DebugPortService
53 | * @return Params object generated by looking at manifest metadata.
54 | */
55 | public static Params start(Context context) {
56 | Params params = getManifestParams(context);
57 | start(context, params);
58 | return params;
59 | }
60 |
61 | /**
62 | * Utility method to start the DebugPortService.
63 | * @param params Parameters to configure the service.
64 | */
65 | public static void start(Context context, @NonNull Params params) {
66 | Intent intent = new Intent(context, DebugPortService.class);
67 | intent.setAction(ACTION_START);
68 | intent.putExtra(INTENT_EXTRA_PARAMS, params);
69 | context.startService(intent);
70 | }
71 |
72 | /**
73 | * Stop the currently-running server.
74 | */
75 | public static void stop(Context context) {
76 | Intent intent = new Intent(context, DebugPortService.class);
77 | intent.setAction(ACTION_STOP);
78 | context.startService(intent);
79 | }
80 |
81 | /**
82 | * Kill the service.
83 | */
84 | public static void kill(Context context) {
85 | Intent intent = new Intent(context, DebugPortService.class);
86 | context.stopService(intent);
87 | }
88 |
89 | private void stopServers() {
90 | if (mDebugServer != null) {
91 | mDebugServer.killServer();
92 | mDebugServer = null;
93 | }
94 | if (mSQLiteServer != null) {
95 | mSQLiteServer.killServer();
96 | mSQLiteServer = null;
97 | }
98 | if (mWakeLock != null) {
99 | mWakeLock.release();
100 | mWakeLock = null;
101 | }
102 | mServersStarted = false;
103 | showNotification();
104 | }
105 |
106 | private void startServers(Params params) {
107 | if (mServersStarted) {
108 | stopServers();
109 | }
110 |
111 | mParams = params;
112 | mServersStarted = true;
113 |
114 | PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
115 | mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DebugPortWakeLock");
116 | mWakeLock.acquire();
117 |
118 | new AsyncTask() {
119 | @SuppressWarnings("deprecation")
120 | @Override
121 | protected void onPostExecute(Void res) {
122 | showNotification();
123 | }
124 |
125 | @Override
126 | protected Void doInBackground(Params... params) {
127 | try {
128 | mDebugServer = new DebugTelnetServer(DebugPortService.this, params[0]);
129 | mDebugServer.startServer();
130 | mSQLiteServer = new SQLiteTelnetServer(DebugPortService.this, params[0]);
131 | mSQLiteServer.startServer();
132 | } catch (java.io.IOException e) {
133 | throw new RuntimeException(e);
134 | }
135 | return null;
136 | }
137 | }.execute(params);
138 | }
139 |
140 | private void showNotification() {
141 | startForeground(NOTIFICATION_ID, buildNotification(mParams));
142 | }
143 |
144 | @Override
145 | public int onStartCommand(Intent intent, int flags, int startId) {
146 | if (ACTION_STOP.equals(intent.getAction())) {
147 | stopServers();
148 | } else if (ACTION_START.equals(intent.getAction())) {
149 | Params params = intent.getParcelableExtra(INTENT_EXTRA_PARAMS);
150 | startServers(params);
151 | } else if (ACTION_INITIALIZE.equals(intent.getAction())) {
152 | showNotification();
153 | } else if (ACTION_KILL.equals(intent.getAction())) {
154 | kill(this);
155 | return START_NOT_STICKY;
156 | } else {
157 | return START_NOT_STICKY;
158 | }
159 | return START_STICKY;
160 | }
161 |
162 | @Override
163 | public void onDestroy() {
164 | super.onDestroy();
165 | stopServers();
166 | stopForeground(true);
167 | }
168 |
169 | private Notification buildNotification(@Nullable Params params) {
170 | NotificationManager mgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
171 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
172 | mgr.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL, getString(R.string.debugport_notification_channel_name), NotificationManager.IMPORTANCE_MIN));
173 | }
174 |
175 | if (params == null) {
176 | params = getManifestParams(this);
177 | }
178 |
179 | String ip = Utils.getIpAddress(this);
180 | String message;
181 | String summary;
182 | if (mServersStarted) {
183 | message = getString(R.string.debugport_notification_subtitle_running, ip, params.getDebugPort(), params.getSQLitePort());
184 | summary = getString(R.string.debugport_notification_summary_running, ip, params.getDebugPort(), params.getSQLitePort());
185 | } else {
186 | message = getString(R.string.debugport_notification_subtitle_stopped);
187 | summary = getString(R.string.debugport_notification_summary_stopped);
188 | }
189 |
190 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
191 | builder.setSmallIcon(R.drawable.debugport_ic_notification);
192 | int appLabel = getApplicationInfo().labelRes;
193 | if (appLabel == 0) {
194 | builder.setContentTitle(getString(R.string.debugport_notification_title_plain));
195 | } else {
196 | builder.setContentTitle(getString(R.string.debugport_notification_title, getString(appLabel)));
197 | }
198 | builder.setStyle(new NotificationCompat.BigTextStyle().bigText(message).setSummaryText(summary));
199 | builder.setPriority(NotificationCompat.PRIORITY_MIN);
200 | builder.setContentText(message);
201 | builder.addAction(mServersStarted ? buildStopAction() : buildStartAction(params));
202 | builder.addAction(buildKillAction());
203 | builder.setChannelId(NOTIFICATION_CHANNEL);
204 |
205 | return builder.build();
206 | }
207 |
208 | private NotificationCompat.Action buildKillAction() {
209 | Intent intent = new Intent(this, DebugPortService.class);
210 | intent.setAction(ACTION_KILL);
211 |
212 | PendingIntent pIntent = PendingIntent.getService(this, STOP_REQUEST_CODE, intent, 0);
213 |
214 | return new NotificationCompat.Action.Builder(0, getString(R.string.debugport_notification_action_kill), pIntent).build();
215 | }
216 |
217 | private NotificationCompat.Action buildStopAction() {
218 | Intent intent = new Intent(this, DebugPortService.class);
219 | intent.setAction(ACTION_STOP);
220 |
221 | PendingIntent pIntent = PendingIntent.getService(this, STOP_REQUEST_CODE, intent, 0);
222 |
223 | return new NotificationCompat.Action.Builder(0, getString(R.string.debugport_notification_action_stop), pIntent)
224 | .build();
225 | }
226 |
227 | private NotificationCompat.Action buildStartAction(@NonNull Params params) {
228 | Intent intent = new Intent(this, DebugPortService.class);
229 | intent.setAction(ACTION_START);
230 | intent.putExtra(INTENT_EXTRA_PARAMS, params);
231 |
232 | PendingIntent pIntent = PendingIntent.getService(this, START_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
233 |
234 | return new NotificationCompat.Action.Builder(0, getString(R.string.debugport_notification_action_start), pIntent)
235 | .build();
236 | }
237 |
238 | @Override
239 | public IBinder onBind(Intent intent) {
240 | return null;
241 | }
242 |
243 | /**
244 | * Called by {@link DebugPortContentProvider} to initialize the service.
245 | */
246 | static void initialize(Context context) {
247 | Intent intent = new Intent(context, DebugPortService.class);
248 | intent.setAction(ACTION_INITIALIZE);
249 | context.startService(intent);
250 | }
251 |
252 | /**
253 | * Load parameters from the manifest metadata.
254 | */
255 | private static Params getManifestParams(Context context) {
256 | Params params = new Params();
257 |
258 | try {
259 | Context app = context.getApplicationContext();
260 | ApplicationInfo ai = app.getPackageManager().getApplicationInfo(app.getPackageName(), PackageManager.GET_META_DATA);
261 | Bundle bundle = ai.metaData;
262 |
263 | params.setDebugPort(bundle.getInt(METADATA_DEBUG_PORT, params.getDebugPort()));
264 | params.setSQLitePort(bundle.getInt(METADATA_SQLITE_PORT, params.getSQLitePort()));
265 |
266 | int startupCommandsResId = bundle.getInt(METADATA_STARTUP_COMMANDS, 0);
267 | if (startupCommandsResId != 0) {
268 | try {
269 | params.setStartupCommands(app.getResources().getStringArray(startupCommandsResId));
270 | } catch (Resources.NotFoundException nfe) {
271 | Log.w(TAG, "Error getting startup commands. Using empty array of commands instead.", nfe);
272 | }
273 | }
274 | } catch (PackageManager.NameNotFoundException | NullPointerException e) {
275 | Log.w(TAG, "Error getting metadata, using default parameters.", e);
276 | }
277 |
278 | return params;
279 | }
280 | }
281 |
--------------------------------------------------------------------------------