random click and gesture for android 7.0+!!!
This commit is contained in:
@@ -89,7 +89,7 @@
|
||||
android:name=".ui.error.IssueReporterActivity"
|
||||
android:theme="@style/IssueReporterTheme"/>
|
||||
|
||||
<service android:name="com.stardust.view.Floaty$FloatHeadService"/>
|
||||
<service android:name="com.stardust.scriptdroid.external.ScriptExecutionIntentService"/>
|
||||
<service android:name="com.stardust.scriptdroid.external.floating_window.menu.HoverMenuService"/>
|
||||
|
||||
<activity android:name=".external.tasker.TaskPrefEditActivity"/>
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
"type": "markdown",
|
||||
"path":"documentation"
|
||||
},
|
||||
{
|
||||
"title": "Android7.0以上点按与手势模拟",
|
||||
"type": "markdown",
|
||||
"path": "documentation"
|
||||
},
|
||||
{
|
||||
"title": "模块与第三方jar",
|
||||
"type": "markdown",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
在Android7.0及以上,可以不需要root权限做到任意坐标的点击,滑动和手势模拟,并且比使用root权限的延迟更短。
|
||||
|
||||
**注意以下命令只有Android7.0及以上才有效**
|
||||
|
||||
### click(x, y)
|
||||
* x \<Number\> 要点击的坐标的x值
|
||||
* y \<Number\> 要点击的坐标的y值
|
||||
|
||||
模拟点击坐标(x, y),并返回是否点击成功。只有在点击执行完成后脚本才继续执行。
|
||||
|
||||
一般而言,只有点击过程(大约150毫秒)中被其他事件中断(例如用户自行点击)才会点击失败。
|
||||
|
||||
> 可以在开发者选项中启用指针位置来查看坐标
|
||||
|
||||
### longClick(x, y)
|
||||
|
||||
* x \<Number\> 要长按的坐标的x值
|
||||
* y \<Number\> 要长按的坐标的y值
|
||||
|
||||
模拟长按坐标(x, y), 并 返回是否成功。只有在长按执行完成(大约600毫秒)时脚本才会继续执行。
|
||||
|
||||
一般而言,只有长按过程中被其他事件中断(例如用户自行点击)才会长按失败。
|
||||
|
||||
### press(x, y, duration)
|
||||
|
||||
* x \<Number\> 要按住的坐标的x值
|
||||
* y \<Number\> 要按住的坐标的y值
|
||||
* duration \<Number\> 按住时长,单位毫秒
|
||||
|
||||
模拟按住坐标(x, y), 并返回是否成功。只有按住操作执行完成时脚本才会继续执行。
|
||||
|
||||
如果按住时间过短,那么会被系统认为是点击;如果时长超过500毫秒,则认为是长按。
|
||||
|
||||
一般而言,只有按住过程中被其他事件中断才会操作失败。
|
||||
|
||||
### swipe(x1, y1, x2, y2, duration)
|
||||
|
||||
* x1 \<Number\> 滑动的起始坐标的x值
|
||||
* y1 \<Number\> 滑动的起始坐标的y值
|
||||
* x2 \<Number\> 滑动的结束坐标的x值
|
||||
* y2 \<Number\> 滑动的结束坐标的y值
|
||||
* duration \<Number\> 滑动时长,单位毫秒
|
||||
|
||||
模拟从坐标(x1, y1)滑动到坐标(x2, y2),并返回是否成功。只有滑动操作执行完成时脚本才会继续执行。
|
||||
|
||||
一般而言,只有滑动过程中被其他事件中断才会滑动失败。
|
||||
|
||||
### gesture(duration, \[x1, y1\], \[x2, y2\], ...)
|
||||
|
||||
* duration \<Number\> 手势的时长
|
||||
* \[x, y\] \<Array\> 手势滑动路径的一系列坐标
|
||||
|
||||
模拟手势操作。例如`gesture(1000, [0, 0], [500, 500], [500, 1000])`为模拟一个从(0, 0)到(500, 500)到(500, 100)的手势操作,时长为2秒。
|
||||
|
||||
### gestures(\[delay, duration, \[x1, y1\], \[x2, y2\], ...], \[delay, duration, \[x3, y3\], \[x4, y4\], ...\], ...)
|
||||
|
||||
同时模拟多个手势。每个手势的参数为\[delay, duration, 坐标\], delay为延迟多久(毫秒)才执行该手势;duration为手势执行时长;坐标为手势经过的点的坐标。其中delay参数可以省略,默认为0。
|
||||
|
||||
参见示例。
|
||||
|
||||
|
||||
### setScreenMetrics(width, height)
|
||||
|
||||
* width \<Number\> 屏幕宽度,单位像素
|
||||
* height <\Number\> 屏幕高度,单位像素
|
||||
|
||||
设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时,屏幕宽度不一致会自动放缩坐标。
|
||||
|
||||
例如在1920*1080的设备中,某个操作的代码为
|
||||
```
|
||||
setScreenMetrics(1080, 1920);
|
||||
click(800, 200);
|
||||
longClick(300, 500);
|
||||
```
|
||||
那么在其他设备上AutoJs会自动放缩坐标以便脚本仍然有效。
|
||||
|
||||
|
||||
|
||||
|
||||
11
app/src/main/assets/sample/安卓7.0+点按和手势/QQ刷一刷.js
Normal file
11
app/src/main/assets/sample/安卓7.0+点按和手势/QQ刷一刷.js
Normal file
@@ -0,0 +1,11 @@
|
||||
"auto";
|
||||
|
||||
launchApp("QQ");
|
||||
sleep(2000);
|
||||
|
||||
while(true){
|
||||
if(currentPackage() == 'com.tencent.mobileqq'){
|
||||
swipe(600, 700, 600, 1400, 400);
|
||||
}
|
||||
sleep(300);
|
||||
}
|
||||
14
app/src/main/assets/sample/安卓7.0+点按和手势/三指下滑.js
Normal file
14
app/src/main/assets/sample/安卓7.0+点按和手势/三指下滑.js
Normal file
@@ -0,0 +1,14 @@
|
||||
"auto";
|
||||
|
||||
/**
|
||||
* 同时模拟三个手势:
|
||||
* 从(300, 400)到(300, 1400)
|
||||
* 从(600, 400)到(600, 1400)
|
||||
* 从(900, 400)到(900, 1400)
|
||||
* 每一个的时长都为350毫秒
|
||||
*/
|
||||
|
||||
gestures([350, [300, 400], [300, 1400]],
|
||||
[350, [600, 400], [600, 1400]],
|
||||
[350, [900, 400], [900, 1400]]
|
||||
);
|
||||
10
app/src/main/assets/sample/安卓7.0+点按和手势/三指捏合.js
Normal file
10
app/src/main/assets/sample/安卓7.0+点按和手势/三指捏合.js
Normal file
@@ -0,0 +1,10 @@
|
||||
"auto";
|
||||
|
||||
setScreenMetrics(1080, 1920);
|
||||
|
||||
//如果你使用的是MIUI,此脚本运行后会出现桌面多屏幕编辑
|
||||
home();
|
||||
sleep(1500);
|
||||
gestures([350, [800, 300], [500, 1000]],
|
||||
[350, [300, 1500], [500, 1000]],
|
||||
[350, [300, 300], [500, 1000]]);
|
||||
9
app/src/main/assets/sample/安卓7.0+点按和手势/双指捏合.js
Normal file
9
app/src/main/assets/sample/安卓7.0+点按和手势/双指捏合.js
Normal file
@@ -0,0 +1,9 @@
|
||||
"auto";
|
||||
|
||||
setScreenMetrics(1080, 1920);
|
||||
|
||||
//如果你使用的是MIUI,此脚本运行后会出现桌面编辑
|
||||
home();
|
||||
sleep(1500);
|
||||
gestures([500, [800, 300], [500, 1000]],
|
||||
[500, [300, 1500], [500, 1000]]);
|
||||
19
app/src/main/assets/sample/安卓7.0+点按和手势/心形手势.js
Normal file
19
app/src/main/assets/sample/安卓7.0+点按和手势/心形手势.js
Normal file
@@ -0,0 +1,19 @@
|
||||
"auto";
|
||||
|
||||
toast("开启开发者选项-指针位置或者在画画软件才能查看效果");
|
||||
|
||||
setScreenMetrics(1080, 1920);
|
||||
|
||||
var points = [10000];
|
||||
var interval = 0.1;
|
||||
var x0 = 600;
|
||||
var y0 = 1000;
|
||||
var a = 120;
|
||||
|
||||
for(var t = 0; t < 2 * Math.PI; t += interval){
|
||||
var x = x0 + a * (2 * Math.cos(t) - Math.cos(2 * t));
|
||||
var y = y0 + a * (2 * Math.sin(t) - Math.sin(2 * t));
|
||||
points.push([parseInt(x), parseInt(y)]);
|
||||
}
|
||||
|
||||
gesture.apply(null, points);
|
||||
4
app/src/main/assets/sample/安卓7.0+点按和手势/拉出通知栏.js
Normal file
4
app/src/main/assets/sample/安卓7.0+点按和手势/拉出通知栏.js
Normal file
@@ -0,0 +1,4 @@
|
||||
"auto";
|
||||
|
||||
//表示从位置(500, 10)滑动到位置(500, 1000), 持续两秒
|
||||
swipe(500, 10, 500, 1000, 2000);
|
||||
5
app/src/main/assets/sample/安卓7.0+点按和手势/点击左上角.js
Normal file
5
app/src/main/assets/sample/安卓7.0+点按和手势/点击左上角.js
Normal file
@@ -0,0 +1,5 @@
|
||||
"auto";
|
||||
|
||||
setScreenMetrics(1080, 1920);
|
||||
|
||||
click(100, 150);
|
||||
@@ -1,24 +1,21 @@
|
||||
"ui";
|
||||
|
||||
importClass(android.widget.EditText);
|
||||
importClass(android.widget.Button);
|
||||
importClass(android.widget.LinearLayout);
|
||||
importClass(android.content.Intent);
|
||||
importClass(android.net.Uri);
|
||||
ui.statusBarColor("#03a9f4");
|
||||
|
||||
var qq = new EditText(activity);
|
||||
qq.setHint("请输入QQ号");
|
||||
var btnOk = new Button(activity);
|
||||
btnOk.setText("确定");
|
||||
var container = new LinearLayout(activity);
|
||||
container.addView(qq);
|
||||
container.addView(btnOk);
|
||||
activity.setContentView(container);
|
||||
ui.layout(
|
||||
<vertical>
|
||||
<text w="*" h="56" bg="#03a9f4" paddingLeft="16" gravity="center_vertical" color="#fff" size="18sp">QQ打开聊天窗口</text>
|
||||
<vertical h="*" w="*" margin="16 50">
|
||||
<input id="qq" h="50" hint="请输入QQ号"/>
|
||||
<button id="ok" text="确定" />
|
||||
</vertical>
|
||||
</vertical>
|
||||
)
|
||||
|
||||
btnOk.setOnClickListener(function(view){
|
||||
ui.ok.click(function(){
|
||||
app.startActivity({
|
||||
action: "android.intent.action.VIEW",
|
||||
data: "mqqwpa://im/chat?chat_type=wpa&uin=" + qq.getText()
|
||||
data: "mqqwpa://im/chat?chat_type=wpa&uin=" + ui.qq.text()
|
||||
});
|
||||
activity.finish();
|
||||
ui.finish();
|
||||
});
|
||||
67
app/src/main/assets/sample/界面/WannaCry(仅为娱乐).js
Normal file
67
app/src/main/assets/sample/界面/WannaCry(仅为娱乐).js
Normal file
@@ -0,0 +1,67 @@
|
||||
"ui";
|
||||
|
||||
/**
|
||||
* By Da Zhang
|
||||
* 本脚本仅为娱乐,没有任何破坏性质
|
||||
*/
|
||||
|
||||
ui.statusBarColor("#AA0000");
|
||||
|
||||
var Quin = 32552732;
|
||||
|
||||
ui.layout(
|
||||
<frame background="#AA0000">
|
||||
<vertical align="top" paddingTop="5" margin="10">
|
||||
<text id="oops" color="#FFFFFF" gravity="center" size="20">Oops, your files have been encrypted!</text>
|
||||
<text id="text" bg="#FFFFFF" gravity="left" color="#000" size="15" marginTop="15" h="425"></text>
|
||||
<button id="payment" text="Payment" margin="20 0 0 0"/>
|
||||
<button id="decrypt" text="Decrypt"/>
|
||||
</vertical>
|
||||
</frame>
|
||||
);
|
||||
ui.text.text("我的手机出了什么问题?\n您的一些重要文件被我加密保存了。\n" +
|
||||
"照片、图片、文档、压缩包、音频、视频文件、apk文件等,几乎所有类型的文件都被加密了,因此不能正常打开。\n" +
|
||||
"这和一般文件损坏有本质上的区别。您大可在网上找找恢复文件的方法,我敢保证,没有我们的解密服务,就算老天爷来了也不能恢复这些文档。\n\n" +
|
||||
"有没有恢复这些文档的方法?\n当然有可恢复的方法。只能通过我们的解密服务才能恢复。我以人格担保,能够提供安全有效的恢复服务。\n" +
|
||||
"但这是收费的,也不能无限期的推迟。\n请点击 <Decrypt> 按钮,就可以免费恢复一些文档。请您放心,我是绝不会骗你的。\n" +
|
||||
"但想要恢复全部文档,需要付款点费用。\n是否随时都可以固定金额付款,就会恢复的吗,当然不是,推迟付款时间越长,对你不利。\n" +
|
||||
"最好3天之内付款费用,过了三天费用就会翻倍。\n还有,一个礼拜之内未付款,将会永远恢复不了。\n" +
|
||||
"对了,忘了告诉你,对半年以上没钱付款的穷人,会有活动免费恢复,能否轮到你,就要看您的运气怎么样了。");
|
||||
ui.oops.click(() => toast("Fuck you!"));
|
||||
ui.oops.longClick(() => {
|
||||
var thisjoke="This is a joke : )";
|
||||
if(ui.oops.text() != thisjoke){
|
||||
ui.oops.text(thisjoke);
|
||||
}else{
|
||||
ui.oops.text("Oops, your files have been encrypted!");
|
||||
}
|
||||
return true;
|
||||
});
|
||||
ui.text.click(() => ui.text.append("。"));
|
||||
ui.text.longClick(() => {
|
||||
ui.text.setText("\n"+ui.text.getText())
|
||||
return true;
|
||||
});
|
||||
ui.payment.click(() => {
|
||||
try{
|
||||
app.startActivity({
|
||||
action:"android.intent.action.VIEW",
|
||||
data:"mqqapi://card/show_pslcard?&uin=" + Quin
|
||||
});
|
||||
toast("Please payment by QQ");
|
||||
}catch(e){
|
||||
toast("Payment Error");
|
||||
}
|
||||
});
|
||||
ui.payment.longClick(() => {
|
||||
toast("You are silly b!");
|
||||
return true;
|
||||
});
|
||||
ui.decrypt.click(() => {
|
||||
toast("Decrypt Error");
|
||||
activity.finish();
|
||||
});
|
||||
ui.decrypt.longClick(() => {
|
||||
toast("You can't decrypt!");
|
||||
return true;
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import android.view.WindowManager;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.autojs.runtime.api.internal.VolatileBox;
|
||||
import com.stardust.concurrent.VolatileBox;
|
||||
import com.stardust.util.UiHandler;
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.view.ContextThemeWrapper;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.autojs.runtime.ScriptInterface;
|
||||
import com.stardust.autojs.runtime.api.AppUtils;
|
||||
import com.stardust.autojs.runtime.api.internal.VolatileBox;
|
||||
import com.stardust.concurrent.VolatileBox;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.util.ArrayUtils;
|
||||
import com.stardust.util.UiHandler;
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.stardust.scriptdroid.external;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.stardust.autojs.script.FileScriptSource;
|
||||
import com.stardust.autojs.script.ScriptSource;
|
||||
@@ -32,6 +33,7 @@ public class CommonUtils {
|
||||
String directoryPath = null;
|
||||
String script = intent.getStringExtra(CommonUtils.EXTRA_KEY_PRE_EXECUTE_SCRIPT);
|
||||
ScriptSource source = null;
|
||||
Toast.makeText(context, path, Toast.LENGTH_SHORT).show();
|
||||
if (path == null && script != null) {
|
||||
source = new StringScriptSource(script);
|
||||
} else if (path != null && new PathChecker(context).checkAndToastError(path)) {
|
||||
|
||||
23
app/src/main/java/com/stardust/scriptdroid/external/ScriptExecutionIntentService.java
vendored
Normal file
23
app/src/main/java/com/stardust/scriptdroid/external/ScriptExecutionIntentService.java
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.stardust.scriptdroid.external;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/15.
|
||||
*/
|
||||
|
||||
public class ScriptExecutionIntentService extends IntentService {
|
||||
|
||||
public ScriptExecutionIntentService() {
|
||||
super("ScriptExecutionIntentService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(@Nullable Intent intent) {
|
||||
if (intent == null)
|
||||
return;
|
||||
CommonUtils.handleIntent(this, intent);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import android.support.annotation.NonNull;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.stardust.util.ScreenMetrics.getScreenHeight;
|
||||
import static com.stardust.util.ScreenMetrics.getScreenWidth;
|
||||
import static com.stardust.util.ScreenMetrics.getDeviceScreenHeight;
|
||||
import static com.stardust.util.ScreenMetrics.getDeviceScreenWidth;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/3.
|
||||
@@ -23,8 +23,8 @@ public class InputEventToSendEventJsConverter extends InputEventConverter {
|
||||
|
||||
public InputEventToSendEventJsConverter() {
|
||||
mCode.append("var sh = new Shell(true);\n")
|
||||
.append("sh.SetScreenMetrics(").append(getScreenWidth()).append(", ")
|
||||
.append(getScreenHeight()).append(");\n");
|
||||
.append("sh.SetScreenMetrics(").append(getDeviceScreenWidth()).append(", ")
|
||||
.append(getDeviceScreenHeight()).append(");\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.support.annotation.NonNull;
|
||||
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.external.CommonUtils;
|
||||
import com.stardust.scriptdroid.external.ScriptExecutionIntentService;
|
||||
import com.stardust.scriptdroid.external.open.RunIntentActivity;
|
||||
import com.twofortyfouram.locale.sdk.client.receiver.AbstractPluginSettingReceiver;
|
||||
|
||||
@@ -30,9 +31,8 @@ public class FireSettingReceiver extends AbstractPluginSettingReceiver {
|
||||
|
||||
@Override
|
||||
protected void firePluginSetting(@NonNull Context context, @NonNull Bundle bundle) {
|
||||
CommonUtils.handleIntent(context, new Intent(App.getApp(), RunIntentActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(CommonUtils.EXTRA_KEY_PATH, bundle.getString(CommonUtils.EXTRA_KEY_PATH))
|
||||
.putExtra(CommonUtils.EXTRA_KEY_PRE_EXECUTE_SCRIPT, bundle.getString(CommonUtils.EXTRA_KEY_PRE_EXECUTE_SCRIPT)));
|
||||
context.startService(new Intent(context, ScriptExecutionIntentService.class)
|
||||
.putExtras(bundle));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.stardust.scriptdroid.service;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import com.nickandjerry.dynamiclayoutinflator.lib.DynamicLayoutInflator;
|
||||
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
|
||||
import com.stardust.view.accessibility.AccessibilityDelegate;
|
||||
import com.stardust.view.accessibility.AccessibilityService;
|
||||
@@ -49,6 +53,9 @@ public class AccessibilityWatchDogService extends AccessibilityService {
|
||||
|
||||
@Override
|
||||
public void onAccessibilityEvent(final AccessibilityEvent event) {
|
||||
if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
|
||||
|
||||
}
|
||||
Log.v(TAG, "onAccessibilityEvent: " + event);
|
||||
if (!containsAllEventTypes && !eventTypes.contains(event.getEventType()))
|
||||
return;
|
||||
@@ -64,6 +71,18 @@ public class AccessibilityWatchDogService extends AccessibilityService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onKeyEvent(KeyEvent event) {
|
||||
Log.v(TAG, "onKeyEvent: " + event);
|
||||
return super.onKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onGesture(int gestureId) {
|
||||
Log.v(TAG, "onGesture: " + gestureId);
|
||||
return super.onGesture(gestureId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt() {
|
||||
}
|
||||
@@ -76,7 +95,7 @@ public class AccessibilityWatchDogService extends AccessibilityService {
|
||||
|
||||
@Override
|
||||
protected void onServiceConnected() {
|
||||
Log.v(TAG, "onServiceConnected");
|
||||
Log.v(TAG, "onServiceConnected: " + getServiceInfo().toString());
|
||||
instance = this;
|
||||
super.onServiceConnected();
|
||||
synchronized (LOCK) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
|
||||
@@ -1,442 +0,0 @@
|
||||
package com.stardust.view;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.view.GestureDetectorCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
/**
|
||||
* Created by ericbhatti on 11/24/15.
|
||||
* <p>
|
||||
* Modified by Stardust on 2017/3/10
|
||||
*
|
||||
* @author Eric Bhatti
|
||||
* @since 24 November, 2015
|
||||
*/
|
||||
public class Floaty {
|
||||
|
||||
public interface FloatyOrientationListener {
|
||||
|
||||
|
||||
/**
|
||||
* This method is called before the orientation change happens, you can use this to save the data of your views so you can later populate the data back in {@link #afterOrientationChange}
|
||||
*
|
||||
* @param floaty The floating window
|
||||
*/
|
||||
public void beforeOrientationChange(Floaty floaty);
|
||||
|
||||
/**
|
||||
* This method is called after the orientation change happens, you can use this to restore the data of your views that you saved in {@link #beforeOrientationChange}
|
||||
*
|
||||
* @param floaty The floating window
|
||||
*/
|
||||
public void afterOrientationChange(Floaty floaty);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public interface OnBackPressedListener {
|
||||
boolean onBackPressed();
|
||||
}
|
||||
|
||||
private View.OnClickListener mOnHeadClickListener;
|
||||
|
||||
|
||||
private OnBackPressedListener mOnBackPressedListener;
|
||||
|
||||
private final View head;
|
||||
private final View body;
|
||||
private final Context context;
|
||||
private final Notification notification;
|
||||
private final int notificationId;
|
||||
private static Floaty floaty;
|
||||
private final FloatyOrientationListener floatyOrientationListener;
|
||||
private float ratioY = 0;
|
||||
private float oldWidth = 0;
|
||||
private float oldX = 0;
|
||||
private boolean confChange = false;
|
||||
|
||||
private static final String LOG_TAG = "Floaty";
|
||||
|
||||
|
||||
/**
|
||||
* @return The body of the floaty which is assigned through the {@link #createInstance} method.
|
||||
*/
|
||||
|
||||
public View getBody() {
|
||||
return floaty.body;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return The head of the floaty which is assigned through the {@link #createInstance} method.
|
||||
*/
|
||||
|
||||
public View getHead() {
|
||||
return floaty.head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Singleton of the Floating Window
|
||||
*
|
||||
* @param context The application context
|
||||
* @param head The head View, upon clicking it the body is to be opened
|
||||
* @param body The body View
|
||||
* @param notificationId The notificationId for your notification
|
||||
* @param notification The notification which is displayed for the foreground service
|
||||
* @param floatyOrientationListener The {@link FloatyOrientationListener} interface with callbacks which are called when orientation changes.
|
||||
* @return A Floating Window
|
||||
*/
|
||||
|
||||
public static synchronized Floaty createInstance(Context context, View head, View body, int notificationId, Notification notification, FloatyOrientationListener
|
||||
floatyOrientationListener) {
|
||||
if (floaty == null) {
|
||||
floaty = new Floaty(context, head, body, notificationId, notification, floatyOrientationListener);
|
||||
}
|
||||
return floaty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Singleton of the Floating Window
|
||||
*
|
||||
* @param context The application context
|
||||
* @param head The head View, upon clicking it the body is to be opened
|
||||
* @param body The body View
|
||||
* @param notificationId The notificationId for your notification
|
||||
* @param notification The notification which is displayed for the foreground service
|
||||
* @return A Floating Window
|
||||
*/
|
||||
public static synchronized Floaty createInstance(Context context, View head, View body, int notificationId, Notification notification) {
|
||||
if (floaty == null) {
|
||||
floaty = new Floaty(context, head, body, notificationId, notification, new FloatyOrientationListener() {
|
||||
@Override
|
||||
public void beforeOrientationChange(Floaty floaty) {
|
||||
Log.d(LOG_TAG, "beforeOrientationChange");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterOrientationChange(Floaty floaty) {
|
||||
Log.d(LOG_TAG, "afterOrientationChange");
|
||||
}
|
||||
});
|
||||
}
|
||||
return floaty;
|
||||
}
|
||||
|
||||
public static synchronized Floaty createInstance(Context context, View head, View body) {
|
||||
return createInstance(context, head, body, -1, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The same instance of Floating Window, which has been created through {@link #createInstance}. Don't call this method before createInstance
|
||||
*/
|
||||
public static synchronized Floaty getInstance() {
|
||||
if (floaty == null) {
|
||||
throw new NullPointerException("Floaty not initialized! First call createInstance method, then to access Floaty in any other class call getDefault()");
|
||||
}
|
||||
return floaty;
|
||||
}
|
||||
|
||||
private Floaty(Context context, View head, View body, int notificationId, Notification notification, FloatyOrientationListener floatyOrientationListener) {
|
||||
this.head = head;
|
||||
this.body = body;
|
||||
this.context = context;
|
||||
this.notification = notification;
|
||||
this.notificationId = notificationId;
|
||||
this.floatyOrientationListener = floatyOrientationListener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the service and adds it to the screen
|
||||
*/
|
||||
public void startService() {
|
||||
Intent intent = new Intent(context, Floaty.FloatHeadService.class);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service and removes it from the screen
|
||||
*/
|
||||
public void stopService() {
|
||||
Intent intent = new Intent(context, Floaty.FloatHeadService.class);
|
||||
context.stopService(intent);
|
||||
}
|
||||
|
||||
|
||||
public void setOnHeadClickListener(View.OnClickListener onHeadClickListener) {
|
||||
mOnHeadClickListener = onHeadClickListener;
|
||||
}
|
||||
|
||||
public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
|
||||
mOnBackPressedListener = onBackPressedListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for notification creation.
|
||||
*
|
||||
* @param context
|
||||
* @param contentTitle
|
||||
* @param contentText
|
||||
* @param notificationIcon
|
||||
* @param contentIntent
|
||||
* @return Notification for the Service
|
||||
*/
|
||||
public static Notification createNotification(Context context, String contentTitle, String contentText, int notificationIcon, PendingIntent contentIntent) {
|
||||
return new NotificationCompat.Builder(context)
|
||||
.setContentTitle(contentTitle)
|
||||
.setContentText(contentText)
|
||||
.setSmallIcon(notificationIcon)
|
||||
.setContentIntent(contentIntent).build();
|
||||
|
||||
}
|
||||
|
||||
public static class FloatHeadService extends Service {
|
||||
|
||||
private WindowManager windowManager;
|
||||
private WindowManager.LayoutParams params;
|
||||
private LinearLayout mLinearLayout;
|
||||
GestureDetectorCompat gestureDetectorCompat;
|
||||
DisplayMetrics metrics;
|
||||
private boolean didFling;
|
||||
private int[] clickLocation = new int[2];
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE || newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
|
||||
int[] location = new int[2];
|
||||
mLinearLayout.getLocationOnScreen(location);
|
||||
floaty.oldWidth = metrics.widthPixels;
|
||||
floaty.confChange = true;
|
||||
if (floaty.getBody().getVisibility() == View.VISIBLE) {
|
||||
floaty.oldX = clickLocation[0];
|
||||
floaty.ratioY = (float) (clickLocation[1]) / (float) metrics.heightPixels;
|
||||
} else {
|
||||
floaty.oldX = location[0];
|
||||
floaty.ratioY = (float) (location[1]) / (float) metrics.heightPixels;
|
||||
}
|
||||
floaty.floatyOrientationListener.beforeOrientationChange(floaty);
|
||||
floaty.stopService();
|
||||
floaty.startService();
|
||||
floaty.floatyOrientationListener.afterOrientationChange(floaty);
|
||||
}
|
||||
}
|
||||
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(LOG_TAG, "onStartCommand");
|
||||
metrics = new DisplayMetrics();
|
||||
windowManager.getDefaultDisplay().getMetrics(metrics);
|
||||
if (floaty.notification != null)
|
||||
startForeground(floaty.notificationId, floaty.notification);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void showHead() {
|
||||
floaty.head.setVisibility(View.VISIBLE);
|
||||
floaty.body.setVisibility(View.GONE);
|
||||
params.x = clickLocation[0];
|
||||
params.y = clickLocation[1] - 36;
|
||||
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
mLinearLayout.setBackgroundColor(Color.argb(0, 0, 0, 0));
|
||||
windowManager.updateViewLayout(mLinearLayout, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d(LOG_TAG, "onCreate");
|
||||
mLinearLayout = new LinearLayout(getApplicationContext()) {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
|
||||
if (floaty.mOnBackPressedListener != null && !floaty.mOnBackPressedListener.onBackPressed()) {
|
||||
showHead();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_HOME) {
|
||||
showHead();
|
||||
return true;
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
gestureDetectorCompat = new GestureDetectorCompat(floaty.context, new GestureDetector.SimpleOnGestureListener() {
|
||||
private int initialX;
|
||||
private int initialY;
|
||||
private float initialTouchX;
|
||||
private float initialTouchY;
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent event) {
|
||||
Log.d(LOG_TAG, "onDown");
|
||||
initialX = params.x;
|
||||
initialY = params.y;
|
||||
initialTouchX = event.getRawX();
|
||||
initialTouchY = event.getRawY();
|
||||
didFling = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
Log.d(LOG_TAG, "onShowPress");
|
||||
floaty.head.setAlpha(0.8f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (floaty.body.getVisibility() == View.VISIBLE) {
|
||||
floaty.body.setVisibility(View.GONE);
|
||||
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
mLinearLayout.setBackgroundColor(Color.argb(0, 0, 0, 0));
|
||||
}
|
||||
params.x = (initialX + (int) ((e2.getRawX() - initialTouchX)));
|
||||
params.y = (initialY + (int) ((e2.getRawY() - initialTouchY)));
|
||||
windowManager.updateViewLayout(mLinearLayout, params);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
Log.d(LOG_TAG, "onSingleTapConfirmed");
|
||||
if (floaty.body.getVisibility() == View.GONE) {
|
||||
params.x = metrics.widthPixels;
|
||||
params.y = 0;
|
||||
params.flags = params.flags & ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
params.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
params.height = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
//floaty.head.getLocationOnScreen(clickLocation);
|
||||
floaty.head.setVisibility(View.GONE);
|
||||
floaty.body.setVisibility(View.VISIBLE);
|
||||
mLinearLayout.setBackgroundColor(Color.argb(200, 50, 50, 50));
|
||||
} else {
|
||||
floaty.body.setVisibility(View.GONE);
|
||||
floaty.head.setVisibility(View.VISIBLE);
|
||||
params.x = clickLocation[0];
|
||||
params.y = clickLocation[1] - 36;
|
||||
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
mLinearLayout.setBackgroundColor(Color.argb(0, 0, 0, 0));
|
||||
}
|
||||
windowManager.updateViewLayout(mLinearLayout, params);
|
||||
if (floaty.mOnHeadClickListener != null) {
|
||||
floaty.mOnHeadClickListener.onClick(floaty.head);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
Log.d(LOG_TAG, "onFling");
|
||||
didFling = true;
|
||||
int newX = params.x;
|
||||
if (newX > (metrics.widthPixels / 2))
|
||||
params.x = metrics.widthPixels;
|
||||
else
|
||||
params.x = 0;
|
||||
windowManager.updateViewLayout(mLinearLayout, params);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mLinearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
metrics = new DisplayMetrics();
|
||||
windowManager.getDefaultDisplay().getMetrics(metrics);
|
||||
params = new WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_PHONE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
params.gravity = Gravity.TOP | Gravity.LEFT;
|
||||
|
||||
|
||||
if (floaty.confChange) {
|
||||
floaty.confChange = false;
|
||||
if (floaty.oldX < (floaty.oldWidth / 2)) {
|
||||
params.x = 0;
|
||||
} else {
|
||||
params.x = metrics.widthPixels;
|
||||
}
|
||||
params.y = (int) (metrics.heightPixels * floaty.ratioY);
|
||||
} else {
|
||||
params.x = metrics.widthPixels;
|
||||
params.y = 0;
|
||||
}
|
||||
floaty.body.setVisibility(View.GONE);
|
||||
floaty.head.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
gestureDetectorCompat.onTouchEvent(event);
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
floaty.head.setAlpha(1.0f);
|
||||
if (!didFling) {
|
||||
Log.d(LOG_TAG, "ACTION_UP");
|
||||
int newX = params.x;
|
||||
if (newX > (metrics.widthPixels / 2))
|
||||
params.x = metrics.widthPixels;
|
||||
else
|
||||
params.x = 0;
|
||||
windowManager.updateViewLayout(mLinearLayout, params);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
windowManager.addView(mLinearLayout, params);
|
||||
if (floaty.body.getParent() != null) {
|
||||
((ViewGroup) floaty.body.getParent()).removeView(floaty.body);
|
||||
}
|
||||
mLinearLayout.setFocusable(true);
|
||||
LinearLayout.LayoutParams headParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
LinearLayout.LayoutParams bodyParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
headParams.gravity = Gravity.TOP | Gravity.RIGHT;
|
||||
bodyParams.gravity = Gravity.TOP;
|
||||
mLinearLayout.addView(floaty.head, headParams);
|
||||
mLinearLayout.addView(floaty.body, bodyParams);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mLinearLayout != null) {
|
||||
mLinearLayout.removeAllViews();
|
||||
windowManager.removeView(mLinearLayout);
|
||||
}
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes="typeAllMask"
|
||||
android:accessibilityEventTypes="typeNotificationStateChanged|typeAnnouncement|typeAssistReadingContext|typeContextClicked|typeGestureDetectionEnd|typeGestureDetectionStart|typeTouchExplorationGestureEnd|typeTouchExplorationGestureStart|typeTouchInteractionEnd|typeTouchInteractionStart|typeViewAccessibilityFocusCleared|typeViewAccessibilityFocused|typeViewClicked|typeViewHoverEnter|typeViewHoverExit|typeViewFocused|typeViewLongClicked|typeViewScrolled|typeViewSelected|typeViewTextChanged|typeViewTextSelectionChanged|typeViewTextTraversedAtMovementGranularity|typeWindowContentChanged|typeWindowsChanged|typeWindowStateChanged"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows"
|
||||
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility|flagRequestFilterKeyEvents"
|
||||
android:canPerformGestures="true"
|
||||
android:canRequestEnhancedWebAccessibility="true"
|
||||
android:canRequestTouchExplorationMode="true"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:description="@string/text_accessibility_service_description"
|
||||
android:notificationTimeout="100"/>
|
||||
@@ -32,18 +32,18 @@ import static org.junit.Assert.assertTrue;
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
// TODO: 2017/3/3 自定义函数
|
||||
// TODO: 2017/3/3 自定义函数 ×
|
||||
// TODO: 2017/3/19 exist函数 √
|
||||
// TODO: 2017/3/23 tasker插件 √
|
||||
// TODO: 2017/3/23 任务管理与控制台 √
|
||||
// TODO: 2017/3/23 悬浮窗加入控制台
|
||||
// TODO: 2017/3/24 文件读写api
|
||||
// TODO: 2017/3/23 悬浮窗加入控制台 √
|
||||
// TODO: 2017/3/24 文件读写api ---
|
||||
// TODO: 2017/3/24 网络读写api
|
||||
// TODO: 2017/3/24 常驻后台api
|
||||
// TODO: 2017/3/24 ui。E4x
|
||||
// TODO: 2017/3/24 编辑界面文档和自动补全
|
||||
// TODO: 2017/3/24 驻留模式
|
||||
//// TODO: 2017/3/26 NODEJS
|
||||
// TODO: 2017/3/24 常驻后台api ×
|
||||
// TODO: 2017/3/24 ui。E4x ---
|
||||
// TODO: 2017/3/24 编辑界面文档和自动补全 ×
|
||||
// TODO: 2017/3/24 驻留模式 ×
|
||||
//// TODO: 2017/3/26 NODEJS ×
|
||||
// TODO: 2017/3/31 自定义快捷方式图标
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user