diff --git a/DEVELOP.md b/DEVELOP.md index fd1b6d1..ff79f20 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -1,32 +1,9 @@ -## minitouch - -测试数据 - - -``` -d 0 800 1000 50 -c -u 0 -c -``` - -d 0 600 800 50 -c -u 0 -c - +## Use APK as commandline 运行 ```bash APK=$(adb shell pm path com.github.uiautomator | cut -d: -f2) adb shell export CLASSPATH="$APK"\; \ - exec app_process /system/bin com.github.uiautomator.MinitouchAgent -``` - -测试 - -```bash -adb forward tcp:7788 localabstract:minitouchagent -nc localhost 7788 -``` + exec app_process /system/bin com.github.uiautomator.Console --version +``` \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/uiautomator/stub/AutomatorServiceImpl.java b/app/src/androidTest/java/com/github/uiautomator/stub/AutomatorServiceImpl.java index 136afdb..96e4ce7 100644 --- a/app/src/androidTest/java/com/github/uiautomator/stub/AutomatorServiceImpl.java +++ b/app/src/androidTest/java/com/github/uiautomator/stub/AutomatorServiceImpl.java @@ -35,7 +35,9 @@ import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; -import androidx.test.InstrumentationRegistry; + +import androidx.core.accessibilityservice.AccessibilityServiceInfoCompat; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.Configurator; import androidx.test.uiautomator.Direction; import androidx.test.uiautomator.StaleObjectException; @@ -69,8 +71,8 @@ public class AutomatorServiceImpl implements AutomatorService { - private final HashSet watchers = new HashSet(); - private final ConcurrentHashMap uiObjects = new ConcurrentHashMap(); + private final HashSet watchers = new HashSet<>(); + private final ConcurrentHashMap uiObjects = new ConcurrentHashMap<>(); private SoundPool soundPool = new SoundPool(100, AudioManager.STREAM_MUSIC, 0); Handler handler = new Handler(Looper.getMainLooper()); @@ -84,13 +86,19 @@ public class AutomatorServiceImpl implements AutomatorService { public AutomatorServiceImpl() { mInstrumentation = InstrumentationRegistry.getInstrumentation(); uiAutomation = mInstrumentation.getUiAutomation(); + + // https://developer.android.com/reference/androidx/core/accessibilityservice/AccessibilityServiceInfoCompat#FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY() + // https://www.jianshu.com/p/a8ccd607e172 + // improve accessibility support for "android.webkit.WebView" + uiAutomation.getServiceInfo().flags |= AccessibilityServiceInfoCompat.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY; + device = UiDevice.getInstance(mInstrumentation); touchController = new TouchController(mInstrumentation); handler.post(new Runnable() { @Override public void run() { - AutomatorServiceImpl.this.clipboard = (ClipboardManager) InstrumentationRegistry.getTargetContext().getSystemService(Context.CLIPBOARD_SERVICE); + AutomatorServiceImpl.this.clipboard = (ClipboardManager) mInstrumentation.getTargetContext().getSystemService(Context.CLIPBOARD_SERVICE); } }); // play music when loaded @@ -119,7 +127,7 @@ private UiAutomation getUiAutomation() { /** * It's to play a section music to test * - * @return + * @return bool */ @Override public boolean playSound(String path) { @@ -167,7 +175,7 @@ public boolean makeToast(final String text, final int duration) { handler.post(new Runnable() { @Override public void run() { - ToastHelper.makeText(InstrumentationRegistry.getTargetContext(), text, duration).show(); + ToastHelper.makeText(mInstrumentation.getTargetContext(), text, duration).show(); } }); return true; @@ -292,12 +300,16 @@ public String dumpWindowHierarchy(boolean compressed, String filename) { */ @Override public String dumpWindowHierarchy(boolean compressed) { - device.setCompressedLayoutHeirarchy(compressed); + device.setCompressedLayoutHierarchy(compressed); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { // Original code: device.dumpWindowHierarchy(os); - // The bellow code fix xml encode error - AccessibilityNodeInfoDumper.dumpWindowHierarchy(device, os); + // The old code have xml encode error + // It seems the new androidx.uiautomator dump looks fine. + device.dumpWindowHierarchy(os); + // alternative + // sometimes java.lang.NullPointerException raises + // AccessibilityNodeInfoDumper.dumpWindowHierarchy(device, os); return os.toString("UTF-8"); } catch (IOException e) { Log.d("dumpWindowHierarchy got IOException: " + e); @@ -323,7 +335,7 @@ public String dumpWindowHierarchy(boolean compressed) { */ @Override public String takeScreenshot(String filename, float scale, int quality) throws NotImplementedException { - File f = new File(InstrumentationRegistry.getTargetContext().getFilesDir(), filename); + File f = new File(mInstrumentation.getTargetContext().getFilesDir(), filename); device.takeScreenshot(f, scale, quality); if (f.exists()) return f.getAbsolutePath(); return null; diff --git a/app/src/androidTest/java/com/github/uiautomator/stub/Log.java b/app/src/androidTest/java/com/github/uiautomator/stub/Log.java index da654fd..6de1282 100644 --- a/app/src/androidTest/java/com/github/uiautomator/stub/Log.java +++ b/app/src/androidTest/java/com/github/uiautomator/stub/Log.java @@ -30,7 +30,7 @@ public static void d(String msg) { android.util.Log.d(TAG, msg); } - public static void i(String msg, String s) { + public static void i(String msg) { android.util.Log.i(TAG, msg); } diff --git a/app/src/androidTest/java/com/github/uiautomator/stub/Stub.java b/app/src/androidTest/java/com/github/uiautomator/stub/Stub.java index 5ab82c4..f8108cc 100644 --- a/app/src/androidTest/java/com/github/uiautomator/stub/Stub.java +++ b/app/src/androidTest/java/com/github/uiautomator/stub/Stub.java @@ -31,7 +31,6 @@ import androidx.test.filters.SdkSuppress; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.By; -import androidx.test.uiautomator.Configurator; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObjectNotFoundException; import androidx.test.uiautomator.Until; @@ -90,7 +89,7 @@ public JsonError resolveError(Throwable throwable, Method method, List } private void launchPackage(String packageName) { - Log.i(TAG, "Launch " + packageName); + Log.i("Launch " + packageName); UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); Context context = InstrumentationRegistry.getContext(); final Intent intent = context.getPackageManager() @@ -111,7 +110,7 @@ private void launchService() throws RemoteException { String launcherPackage = device.getLauncherPackageName(); Boolean ready = device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT); if (!ready) { - Log.i(TAG, "Wait for launcher timeout"); + Log.i("Wait for launcher timeout"); return; } @@ -141,6 +140,7 @@ private void stopMonitorService(Context context) { @Test @LargeTest public void testUIAutomatorStub() throws InterruptedException { + Log.i("server started"); while (server.isAlive()) { Thread.sleep(100); } diff --git a/app/src/main/java/com/github/uiautomator/Console.java b/app/src/main/java/com/github/uiautomator/Console.java index 5cf557f..1a08578 100644 --- a/app/src/main/java/com/github/uiautomator/Console.java +++ b/app/src/main/java/com/github/uiautomator/Console.java @@ -81,11 +81,7 @@ private void listenAndServe(){ System.exit(1); } - MinitouchAgent minitouch = new MinitouchAgent(size.x, size.y, handler, "minitouchagent"); - MinicapAgent minicap = new MinicapAgent(size.x, size.y, "minicapagent"); RotationAgent rotation = new RotationAgent("rotationagent"); - minitouch.start(); - minicap.start(); rotation.start(); Looper.loop(); diff --git a/app/src/main/java/com/github/uiautomator/IdentifyActivity.java b/app/src/main/java/com/github/uiautomator/IdentifyActivity.java index d52cc8d..12e4d71 100644 --- a/app/src/main/java/com/github/uiautomator/IdentifyActivity.java +++ b/app/src/main/java/com/github/uiautomator/IdentifyActivity.java @@ -48,7 +48,7 @@ protected void onCreate(Bundle savedInstanceState) { Log.i(TAG, "theme " + activityTheme); Float brightness = 0.1f; Integer backgroundColor = Color.BLACK; - if (activityTheme != null && "RED".equals(activityTheme)) { + if ("RED".equals(activityTheme)) { backgroundColor = Color.RED; brightness = 1.0f; } @@ -73,12 +73,12 @@ protected void onCreate(Bundle savedInstanceState) { layout.addView(createData(Build.VERSION.RELEASE + " (SDK " + Build.VERSION.SDK_INT + ")")); layout.addView(createLabel("OPERATOR")); layout.addView(createData(tm.getSimOperatorName())); - layout.addView(createLabel("PHONE")); - layout.addView(createData(tm.getLine1Number())); - layout.addView(createLabel("IMEI")); - layout.addView(createData(tm.getDeviceId())); - layout.addView(createLabel("ICCID")); - layout.addView(createData(tm.getSimSerialNumber())); +// layout.addView(createLabel("PHONE")); +// layout.addView(createData(tm.getLine1Number())); +// layout.addView(createLabel("IMEI")); +// layout.addView(createData(tm.getDeviceId())); +// layout.addView(createLabel("ICCID")); +// layout.addView(createData(tm.getSimSerialNumber())); requestWindowFeature(Window.FEATURE_NO_TITLE); ensureVisibility(brightness); @@ -119,8 +119,13 @@ private void ensureVisibility(Float brightness) { private String getProperty(String name, String defaultValue) { try { Class SystemProperties = Class.forName("android.os.SystemProperties"); - Method get = SystemProperties.getMethod("get", String.class, String.class); - return (String) get.invoke(SystemProperties, name, defaultValue); + try { + Method get = SystemProperties.getMethod("get", String.class, String.class); + return (String) get.invoke(SystemProperties, name, defaultValue); + } catch (NoSuchMethodException e) { + Method get = SystemProperties.getMethod("get", String.class); + return (String) get.invoke(SystemProperties, name); + } } catch (ClassNotFoundException e) { Log.e(TAG, "Class.forName() failed", e); return defaultValue; diff --git a/app/src/main/java/com/github/uiautomator/MainActivity.java b/app/src/main/java/com/github/uiautomator/MainActivity.java index 912d792..e3296fa 100644 --- a/app/src/main/java/com/github/uiautomator/MainActivity.java +++ b/app/src/main/java/com/github/uiautomator/MainActivity.java @@ -3,9 +3,7 @@ import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.net.wifi.WifiManager; @@ -17,113 +15,45 @@ import android.text.format.Formatter; import android.util.Log; import android.view.View; -import android.view.View.OnClickListener; -import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; -import android.widget.Toast; - import com.android.permission.FloatWindowManager; import com.github.uiautomator.util.MemoryManager; -import com.github.uiautomator.util.OkhttpManager; import com.github.uiautomator.util.Permissons4App; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; public class MainActivity extends Activity { private final String TAG = "ATXMainActivity"; - private final String ATX_AGENT_URL = "http://127.0.0.1:7912"; private TextView tvInStorage; private TextView textViewIP; - private TextView tvAgentStatus; - private TextView tvAutomatorStatus; - private TextView tvAutomatorMode; - private TextView tvServiceMessage; - private WindowManager windowManager = null; - private boolean isWindowShown = false; private FloatView floatView; - private OkhttpManager okhttpManager = OkhttpManager.getSingleton(); - - private static final class TextViewSetter implements Runnable { - private final TextView v; - private final String what; - private final int color; - - TextViewSetter(TextView v, String what, int color) { - this.v = v; - this.what = what; - this.color = color; - } - - TextViewSetter(TextView v, String what) { - this(v, what, Color.BLACK); - } - - @Override - public void run() { - v.setText(what); - v.setTextColor(color); - } - } - @RequiresApi(api = Build.VERSION_CODES.O) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - tvAgentStatus = findViewById(R.id.atx_agent_status); - tvAutomatorStatus = findViewById(R.id.uiautomator_status); - tvAutomatorMode = findViewById(R.id.uiautomator_mode); - tvServiceMessage = findViewById(R.id.serviceMessage); - Button btnFinish = findViewById(R.id.btn_finish); - btnFinish.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - stopService(new Intent(MainActivity.this, Service.class)); - finish(); - } + btnFinish.setOnClickListener(view -> { + stopService(new Intent(MainActivity.this, Service.class)); + finish(); }); Button btnIdentify = findViewById(R.id.btn_identify); - btnIdentify.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, IdentifyActivity.class); - Bundle bundle = new Bundle(); - bundle.putString("theme", "RED"); - intent.putExtras(bundle); - startActivity(intent); - } + btnIdentify.setOnClickListener(v -> { + Intent intent = new Intent(MainActivity.this, IdentifyActivity.class); + Bundle bundle = new Bundle(); + bundle.putString("theme", "RED"); + intent.putExtras(bundle); + startActivity(intent); }); - findViewById(R.id.accessibility).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); - } - }); + findViewById(R.id.accessibility).setOnClickListener(v -> startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))); - findViewById(R.id.development_settings).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)); - } - }); + findViewById(R.id.development_settings).setOnClickListener(v -> startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS))); Intent intent = getIntent(); boolean isHide = intent.getBooleanExtra("hide", false); @@ -155,7 +85,6 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } public void showFloatWindow(View view) { - boolean floatEnabled = FloatWindowManager.getInstance().checkFloatPermission(MainActivity.this); if (!floatEnabled) { Log.i(TAG, "float permission not checked"); @@ -167,57 +96,6 @@ public void showFloatWindow(View view) { floatView.show(); } - public void stopUiautomator(View view) { - Request request = new Request.Builder() - .url(ATX_AGENT_URL + "/uiautomator") - .delete() - .build(); - okhttpManager.newCall(request, new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - uiToaster("UIAutomator already stopped "); - checkUiautomatorStatus(null); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - checkUiautomatorStatus(null); - } - }); - } - - public void startUiautomator(View view) { - Request request = new Request.Builder() - .url(ATX_AGENT_URL + "/uiautomator") - .post(RequestBody.create(null, new byte[0])) - .build(); - okhttpManager.newCall(request, new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - uiToaster("UIAutomator not starting"); - checkUiautomatorStatus(null); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - uiToaster("UIAutomator started"); - checkUiautomatorStatus(null); - } - }); - } - - private void uiToaster(final String msg) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show(); - } - }); - } - - public void dismissFloatWindow(View view) { if (floatView != null) { Log.d(TAG, "remove floatView immediate"); @@ -225,163 +103,10 @@ public void dismissFloatWindow(View view) { } } - public void atxAgentStopConfirm(View view) { - AlertDialog.Builder localBuilder = new AlertDialog.Builder(this); - localBuilder.setTitle("Stopping AtxAgent"); - localBuilder.setMessage("AtxAgent下次必须通过adb启动"); - localBuilder.setPositiveButton("YES", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - stopAtxAgent(); - } - }); - localBuilder.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - localBuilder.show(); - } - - private void stopAtxAgent() { - Request request = new Request.Builder() - .url(ATX_AGENT_URL + "/stop") - .get() - .build(); - okhttpManager.newCall(request, new Callback() { - - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - uiToaster("AtxAgent already stopped"); - checkAtxAgentStatus(null); - } - - @Override - public void onResponse(Call call, Response response) { - uiToaster("AtxAgent stopped"); - checkAtxAgentStatus(null); - } - }); - } - - public void checkAtxAgentStatus(View view) { - Request request = new Request.Builder() - .url(ATX_AGENT_URL + "/ping") - .get() - .build(); - okhttpManager.newCall(request, new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - runOnUiThread(new TextViewSetter(tvAgentStatus, "AtxAgent Stopped")); - runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } - - @Override - public void onResponse(Call call, Response response) { - runOnUiThread(new TextViewSetter(tvAgentStatus, "AtxAgent Running")); - try { - runOnUiThread(new TextViewSetter(tvServiceMessage, response.body().string())); - } catch (IOException e) { - e.printStackTrace(); - runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } - } - }); - } - - public void testUiautomator(View view) { - String json = "{" + - " \"jsonrpc\": \"2.0\",\n" + - " \"id\": \"14d3bbb25360373624ea5b343c5abb1f\", \n" + - " \"method\": \"dumpWindowHierarchy\",\n" + - " \"params\": [false]\n" + - " }"; - Request request = new Request.Builder() - .url(ATX_AGENT_URL + "/jsonrpc/0") - .post(RequestBody.create(MediaType.parse("application/json"), json)) - .build(); - okhttpManager.newCall(request, new Callback() { - - @Override - public void onFailure(Call call, IOException e) { - runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } - - @Override - public void onResponse(Call call, Response response) { - try { - if (response.body() == null || !response.isSuccessful()) { - runOnUiThread(new TextViewSetter(tvServiceMessage, "UIAutomator not responding!")); - return; - } - String responseData = response.body().string(); - runOnUiThread(new TextViewSetter(tvServiceMessage, responseData)); -// JSONObject obj = new JSONObject(responseData); -// } catch (JSONException e) { -// e.printStackTrace(); -// runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } catch (IOException e) { - e.printStackTrace(); - runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } - } - }); - } - - public void checkUiautomatorStatus(View view) { - Request request = new Request.Builder() - .url(ATX_AGENT_URL + "/uiautomator") - .get() - .build(); - okhttpManager.newCall(request, new Callback() { - - @Override - public void onFailure(Call call, IOException e) { - runOnUiThread(new TextViewSetter(tvAutomatorStatus, "UIAutomator Stopped")); - runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } - - @Override - public void onResponse(Call call, Response response) { - try { - if (response.body() == null || !response.isSuccessful()) { - this.onFailure(call, new IOException("UIAutomator not responding!")); - return; - } - String responseData = response.body().string(); - JSONObject obj = new JSONObject(responseData); - boolean running = obj.getBoolean("running"); - String status = running ? "UIAutomator Running" : "UIAutomator Stopped"; - runOnUiThread(new TextViewSetter(tvAutomatorStatus, status)); - runOnUiThread(new TextViewSetter(tvServiceMessage, responseData)); - try { - Class.forName("com.github.uiautomator.stub.Stub"); - runOnUiThread(new TextViewSetter(tvAutomatorMode, "正常服务模式")); - } catch (ClassNotFoundException e) { - // TODO 应在onResume check后弹框强制退出 - runOnUiThread(new TextViewSetter(tvAutomatorMode, "无法提供服务 非am instrument启动", Color.RED)); - } - } catch (JSONException e) { - e.printStackTrace(); - runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } catch (IOException e) { - e.printStackTrace(); - runOnUiThread(new TextViewSetter(tvServiceMessage, e.toString())); - } - } - }); - } - @SuppressLint("SetTextI18n") @Override protected void onResume() { super.onResume(); - checkAtxAgentStatus(null); - checkUiautomatorStatus(null); - tvInStorage.setText(Formatter.formatFileSize(this, MemoryManager.getAvailableInternalMemorySize()) + "/" + Formatter.formatFileSize(this, MemoryManager.getTotalExternalMemorySize())); checkNetworkAddress(null); } diff --git a/app/src/main/java/com/github/uiautomator/MinicapAgent.java b/app/src/main/java/com/github/uiautomator/MinicapAgent.java deleted file mode 100644 index 1d873d0..0000000 --- a/app/src/main/java/com/github/uiautomator/MinicapAgent.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.github.uiautomator; - -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.graphics.Point; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.os.Process; -import android.os.SystemClock; -import android.util.Log; - -import com.github.uiautomator.compat.WindowManagerWrapper; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - - -public class MinicapAgent extends Thread { - private static final String PROCESS_NAME = "minicap.cli"; - private static final String VERSION = "1.0"; - private static final String DEFAULT_SOCKET_NAME = "minicapagent"; - private static final String TAG = "minicap"; - - final WindowManagerWrapper windowManager = new WindowManagerWrapper(); - private int width; - private int height; - private int rotation; - private String socketName; - - - private static void setArgV0(String text) { - try { - Method setter = android.os.Process.class.getMethod("setArgV0", String.class); - setter.invoke(android.os.Process.class, text); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - - public MinicapAgent(int width, int height, String socketName) { - this.width = width; - this.height = height; - this.rotation = windowManager.getRotation(); - this.socketName = socketName; - windowManager.watchRotation(r -> { - rotation = r; - System.out.println("Rotation:" + r); - }); - } - - private void manageClientConnection(LocalServerSocket serverSocket) { - while (true) { - System.out.printf("Listening on localabstract:%s\n", socketName); - - // python3 -m adbutils.pidcat -t scrcpy - Log.i(TAG, String.format("Listening on %s", socketName)); - try (LocalSocket socket = serverSocket.accept()) { - Log.d(TAG, "client connected"); - - OutputStream output = socket.getOutputStream(); - rotation = windowManager.getRotation(); - Point size = windowManager.getDisplaySize(); - System.out.println("Display: " + size.x + "x" + size.y); - System.out.println("Rotation: " + rotation); - try { - writeBanner(output); - pipeImages(output); - } catch (Exception e) { - System.out.println("socket closed, exception: " + e); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private void writeBanner(OutputStream stream) throws IOException { - final byte BANNER_SIZE = 24; - final byte version = 1; - final byte quirks = 1; // QUIRK_DUMB - int pid = Process.myPid(); - - ByteBuffer b = ByteBuffer.allocate(BANNER_SIZE); - b.order(ByteOrder.LITTLE_ENDIAN); - b.put((byte) version); // version - b.put(BANNER_SIZE);//banner size - b.putInt(pid);//pid - b.putInt(0);//real width - b.putInt(0);//real height - b.putInt(0);//desired width - b.putInt(0);//desired height - b.put((byte) rotation);//orientation - b.put((byte) quirks);//quirks - byte[] array = b.array(); - stream.write(array); - } - - private void pipeImages(OutputStream stream) throws Exception { - final int quality = 70; - final int maxFPS = 20; - final long interval = 1000 / maxFPS; - - long count = 0; - long lastStartTime = 0; - long calulateStartTime = SystemClock.uptimeMillis(); - - while (true) { - long waitMillis = lastStartTime + interval - SystemClock.uptimeMillis(); - if (waitMillis > 0) { - SystemClock.sleep(waitMillis); - } - lastStartTime = SystemClock.uptimeMillis(); - Bitmap bmp = takeScreenshot(); - try { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - bmp.compress(Bitmap.CompressFormat.JPEG, quality, buf); - byte[] jpegData = buf.toByteArray(); - - ByteBuffer b = ByteBuffer.allocate(4); - b.order(ByteOrder.LITTLE_ENDIAN); - b.putInt(jpegData.length); - stream.write(b.array()); // write image size - stream.write(jpegData); // write image data - - count++; - if (count == 30) { - float fps = (float) count / (SystemClock.uptimeMillis() - calulateStartTime) * 1000; - System.out.println("FPS: " + fps); - calulateStartTime = SystemClock.uptimeMillis(); - count = 0; - } - } finally { - bmp.recycle(); - } - } - } - - private Bitmap takeScreenshot() throws Exception { - Class surfaceControl = Class.forName("android.view.SurfaceControl"); - Method screenshotMethod = surfaceControl.getDeclaredMethod("screenshot", Integer.TYPE, Integer.TYPE); - try { - Bitmap bmp = (Bitmap) screenshotMethod.invoke(null, new Object[]{width, height}); - if (rotation == 0) { - return bmp; - } - Matrix matrix = new Matrix(); - matrix.postRotate(-90 * this.rotation); - Bitmap rotatedBmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, false); - bmp.recycle(); - return rotatedBmp; - } catch (Exception e) { - throw new Exception("Inject SurfaceControl fail", e); - } - } - - public static void main(String[] args) { - /** - * Usage: - * APKPATH=$(adb shell pm path com.github.uiautomator | cut -d: -f2) - * adb shell CLASSPATH=$APKPATH exec app_process /system/bin com.github.uiautomator.MinicapAgent --help - */ - setArgV0(PROCESS_NAME); - - WindowManagerWrapper wm = new WindowManagerWrapper(); - try { - System.out.println("Dump --."); - System.out.println("Rotation: " + wm.getRotation()); - Point size = wm.getDisplaySize(); - if (size != null) { - System.out.println("Display: " + size.x + "x" + size.y); - } - } catch (Exception e) { - e.printStackTrace(); - } - - Point size = wm.getDisplaySize(); - if (size == null) { - System.err.println("Unable to get screen resolution"); - System.exit(1); - } - - MinicapAgent agent = new MinicapAgent(size.x, size.y, DEFAULT_SOCKET_NAME); - agent.run(); - } - - @Override - public void run() { - try (LocalServerSocket serverSocket = new LocalServerSocket(socketName)) { - manageClientConnection(serverSocket); - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/main/java/com/github/uiautomator/MinitouchAgent.java b/app/src/main/java/com/github/uiautomator/MinitouchAgent.java deleted file mode 100644 index 023f365..0000000 --- a/app/src/main/java/com/github/uiautomator/MinitouchAgent.java +++ /dev/null @@ -1,298 +0,0 @@ -package com.github.uiautomator; - -import android.graphics.Point; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import androidx.annotation.RequiresApi; -import android.util.Log; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.MotionEvent; - -import com.github.uiautomator.compat.InputManagerWrapper; -import com.github.uiautomator.compat.WindowManagerWrapper; - -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.NoSuchElementException; -import java.util.Scanner; - -@RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH) -public class MinitouchAgent extends Thread { - private static final String TAG = MinitouchAgent.class.getSimpleName(); - private static final String SOCKET = "minitouchagent"; - private static final int DEFAULT_MAX_CONTACTS = 10; - private static final int DEFAULT_MAX_PRESSURE = 0; - private final int width; - private final int height; - private String socketName; - private LocalServerSocket serverSocket; - - private MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[2]; - private MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; - private PointerEvent[] events = new PointerEvent[2]; - - private final InputManagerWrapper inputManager; - private final WindowManagerWrapper windowManager; - private final Handler handler; - - private class PointerEvent { - long lastMouseDown; - int lastX; - int lastY; - int action; - } - - private void injectEvent(final InputEvent event) { - handler.post(new Runnable() { - @Override - public void run() { - Log.i(TAG, "injectInputEvent: " + event); - inputManager.injectInputEvent(event); - } - }); - } - - private MotionEvent getMotionEvent(PointerEvent p) { - return getMotionEvent(p, 0); - } - - private MotionEvent getMotionEvent(PointerEvent p, int idx) { - long now = SystemClock.uptimeMillis(); - if (p.action == MotionEvent.ACTION_DOWN) { - p.lastMouseDown = now; - } - MotionEvent.PointerCoords coords = pointerCoords[idx]; - int rotation = windowManager.getRotation(); - double rad = Math.toRadians(rotation * 90.0); - coords.x = (float) (p.lastX * Math.cos(-rad) - p.lastY * Math.sin(-rad)); - coords.y = (rotation * width) + (float) (p.lastX * Math.sin(-rad) + p.lastY * Math.cos(-rad)); - return MotionEvent.obtain(p.lastMouseDown, now, p.action, idx + 1, pointerProperties, - pointerCoords, 0, 0, 1f, 1f, 0, 0, - InputDevice.SOURCE_TOUCHSCREEN, 0); - } - - private List getMotionEvent(PointerEvent p1, PointerEvent p2) { - List combinedEvents = new ArrayList<>(2); - long now = SystemClock.uptimeMillis(); - if (p1.action != MotionEvent.ACTION_MOVE) { - combinedEvents.add(getMotionEvent(p1)); - combinedEvents.add(getMotionEvent(p2, 1)); - } else { - MotionEvent.PointerCoords coords1 = pointerCoords[0]; - MotionEvent.PointerCoords coords2 = pointerCoords[1]; - int rotation = windowManager.getRotation(); - double rad = Math.toRadians(rotation * 90.0); - - coords1.x = (float) (p1.lastX * Math.cos(-rad) - p1.lastY * Math.sin(-rad)); - coords1.y = (rotation * width) + (float) (p1.lastX * Math.sin(-rad) + p1.lastY * Math.cos(-rad)); - - coords2.x = (float) (p2.lastX * Math.cos(-rad) - p2.lastY * Math.sin(-rad)); - coords2.y = (rotation * width) + (float) (p2.lastX * Math.sin(-rad) + p2.lastY * Math.cos(-rad)); - - MotionEvent event = MotionEvent.obtain(p1.lastMouseDown, now, p1.action, 2, pointerProperties, - pointerCoords, 0, 0, 1f, 1f, 0, 0, - InputDevice.SOURCE_TOUCHSCREEN, 0); - combinedEvents.add(event); - } - return combinedEvents; - } - - private static String getPid() throws IOException { - byte[] bo = new byte[256]; - InputStream is = new FileInputStream("/proc/self/stat"); - is.read(bo); - for (int i = 0; i < bo.length; i++) { - if ((bo[i] < '0') || (bo[i] > '9')) { - return new String(bo, 0, i); - } - } - return "-1"; - } - - /** - * v 1 - * ^ 10 1080 2340 0 - * $ 1078 - */ - private void sendBanner(LocalSocket clientSocket) { - try { - OutputStreamWriter out = new OutputStreamWriter(clientSocket.getOutputStream()); - out.write("v 1\n"); - String resolution = String.format(Locale.US, "^ %d %d %d %d%n", - DEFAULT_MAX_CONTACTS, width, height, DEFAULT_MAX_PRESSURE); - out.write(resolution); - out.write(String.format(Locale.US, "$ %s%n", getPid())); - out.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Manages the client connection. The client is supposed to be minitouch. - */ - private void manageClientConnection() { - while (true) { - System.out.println("Listening on localabstract:" + socketName); - Log.i(TAG, String.format("Listening on localabstract:%s", socketName)); - LocalSocket clientSocket; - try { - clientSocket = serverSocket.accept(); - Log.d(TAG, "client connected"); - sendBanner(clientSocket); - processCommandLoop(clientSocket); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * processCommandLoop parses touch related commands sent by stf - * and inject them in Android InputManager. - * Commmands can be of type down, up, move, commit - * Note that it currently doesn't support multitouch - * - * @param clientSocket the socket to read on - */ - private void processCommandLoop(LocalSocket clientSocket) throws IOException { - try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) { - String cmd; - int count = 0; - while ((cmd = in.readLine()) != null) { - try (Scanner scanner = new Scanner(cmd)) { - scanner.useDelimiter(" "); - String type = scanner.next(); - int contact; - switch (type) { - case "c": - if (count == 1) { - MotionEvent event = getMotionEvent(events[0]); - Log.i(TAG, "motion event: " + event); - injectEvent(event); - } else if (count == 2) { - for (MotionEvent event : getMotionEvent(events[0], events[1])) { - injectEvent(event); - } - } else { - System.out.println("count not manage events #" + count); - } - count = 0; - break; - case "u": - count++; - contact = scanner.nextInt(); - events[contact].action = (contact == 0) ? MotionEvent.ACTION_UP : MotionEvent.ACTION_POINTER_2_UP; - break; - case "d": - count++; - contact = scanner.nextInt(); - events[contact].lastX = scanner.nextInt(); - events[contact].lastY = scanner.nextInt(); - //scanner.nextInt(); //pressure is currently not supported - events[contact].action = (contact == 0) ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_POINTER_2_DOWN; - break; - case "m": - count++; - contact = scanner.nextInt(); - events[contact].lastX = scanner.nextInt(); - events[contact].lastY = scanner.nextInt(); - //scanner.nextInt(); //pressure is currently not supported - events[contact].action = MotionEvent.ACTION_MOVE; - break; - case "w": - int delayMs = scanner.nextInt(); - Thread.sleep(delayMs); - break; - default: - System.out.println("could not parse: " + cmd); - } - } catch (NoSuchElementException e) { - System.out.println("could not parse: " + cmd); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } - - public MinitouchAgent(int width, int height, Handler handler, String socketName) { - this.width = width; - this.height = height; - this.handler = handler; - this.socketName = socketName; - inputManager = new InputManagerWrapper(); - windowManager = new WindowManagerWrapper(); - MotionEvent.PointerProperties pointerProps0 = new MotionEvent.PointerProperties(); - pointerProps0.id = 0; - pointerProps0.toolType = MotionEvent.TOOL_TYPE_FINGER; - MotionEvent.PointerProperties pointerProps1 = new MotionEvent.PointerProperties(); - pointerProps1.id = 1; - pointerProps1.toolType = MotionEvent.TOOL_TYPE_FINGER; - pointerProperties[0] = pointerProps0; - pointerProperties[1] = pointerProps1; - - MotionEvent.PointerCoords pointerCoords0 = new MotionEvent.PointerCoords(); - MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords(); - pointerCoords0.orientation = 0; - pointerCoords0.pressure = 1; // pressure and size have to be set - pointerCoords0.size = 1; - pointerCoords1.orientation = 0; - pointerCoords1.pressure = 1; - pointerCoords1.size = 1; - pointerCoords[0] = pointerCoords0; - pointerCoords[1] = pointerCoords1; - - events[0] = new PointerEvent(); - events[1] = new PointerEvent(); - } - - /** - * Keep a way to start only the MinitouchAgent for debugging purpose - */ - public static void main(String[] args) { - //To create a Handler our main thread has to prepare the Looper - Looper.prepare(); - Handler handler = new Handler(); - Point size = new WindowManagerWrapper().getDisplaySize(); - System.out.println("Screen size: " + size.x + ", " + size.y); - - if (size != null) { - MinitouchAgent m = new MinitouchAgent(size.x, size.y, handler, "minitouchagent"); - m.start(); - Looper.loop(); - } else { - System.err.println("Couldn't get screen resolution"); - System.exit(1); - } - } - - @Override - public void run() { - try { - Log.i(TAG, String.format("creating socket %s", SOCKET)); - serverSocket = new LocalServerSocket(SOCKET); - } catch (IOException e) { - e.printStackTrace(); - return; - } - manageClientConnection(); - try { - serverSocket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/main/java/com/github/uiautomator/ToastActivity.java b/app/src/main/java/com/github/uiautomator/ToastActivity.java index eb1d9ca..04e71fc 100644 --- a/app/src/main/java/com/github/uiautomator/ToastActivity.java +++ b/app/src/main/java/com/github/uiautomator/ToastActivity.java @@ -7,6 +7,8 @@ import android.util.Log; import android.widget.Toast; +import com.android.permission.FloatWindowManager; + public class ToastActivity extends Activity { final static String TAG = "ToastActivity"; private static FloatView floatView; @@ -26,6 +28,12 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { String showFloat = intent.getStringExtra("showFloatWindow"); Log.i(TAG, "showFloat: " + showFloat); + + boolean floatEnabled = FloatWindowManager.getInstance().checkFloatPermission(ToastActivity.this); + if (!floatEnabled) { + Log.w(TAG, "floatPermission is not enabled"); + return; + } if ("true".equals(showFloat)) { getFloatView().show(); } else if ("false".equals(showFloat)) { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0e6a18e..56a278e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -10,32 +10,6 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - -