# Conflicts:
#	.idea/caches/build_file_checksums.ser
This commit is contained in:
hyb1996
2018-10-11 10:10:40 +08:00
87 changed files with 1825 additions and 752 deletions

3
.gitignore vendored
View File

@@ -11,4 +11,5 @@
/captures
.externalNativeBuild
*.apk
*.exe
*.exe
.idea/caches/build_file_checksums.ser

Binary file not shown.

View File

@@ -3,6 +3,7 @@
<words>
<w>capturer</w>
<w>dismissable</w>
<w>flowable</w>
<w>interruptible</w>
<w>loopers</w>
<w>prefill</w>

View File

@@ -8,8 +8,8 @@ android {
applicationId "org.autojs.autojs"
minSdkVersion 17
targetSdkVersion 23
versionCode 420
versionName "4.0.3 Alpha"
versionCode 421
versionName "4.0.3 Alpha2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
@@ -24,7 +24,7 @@ android {
}
release {
shrinkResources false
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
@@ -83,10 +83,10 @@ dependencies {
})
annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
// Android supports
compile 'com.android.support:appcompat-v7:25.4.0'
compile 'com.android.support:cardview-v7:25.4.0'
compile 'com.android.support:design:25.4.0'
compile 'com.android.support:multidex:1.0.2'
compile 'com.android.support:appcompat-v7:27.1.1'
compile 'com.android.support:cardview-v7:27.1.1'
compile 'com.android.support:design:27.1.1'
compile 'com.android.support:multidex:1.0.3'
// Personal libraries
compile 'com.github.hyb1996:MutableTheme:0.2.2'
// Material Dialogs
@@ -129,11 +129,6 @@ dependencies {
exclude group: 'com.android.support'
})
annotationProcessor 'com.github.bumptech.glide:compiler:4.2.0'
//dbflow
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:4.1.2"
compile "com.github.Raizlabs.DBFlow:dbflow-core:4.1.2"
compile "com.github.Raizlabs.DBFlow:dbflow:4.1.2"
compile "com.github.Raizlabs.DBFlow:dbflow-rx2:4.1.2"
//joda time
compile 'joda-time:joda-time:2.9.9'
// Tasker Plugin
@@ -146,6 +141,10 @@ dependencies {
compile('com.afollestad.material-dialogs:commons:0.9.2.3', {
exclude group: 'com.android.support'
})
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
compile project(':automator')
compile project(':common')
compile project(':autojs')

View File

@@ -116,6 +116,7 @@
android:theme="@style/IssueReporterTheme"/>
<service android:name=".external.foreground.ForegroundService"/>
<service android:name=".external.ScriptExecutionIntentService"/>
<activity android:name=".external.tasker.TaskPrefEditActivity_"/>
@@ -132,10 +133,44 @@
</intent-filter>
</activity-alias>
<receiver android:name="org.autojs.autojs.external.boot.BootCompleteReceiver" >
<receiver android:name="org.autojs.autojs.external.receiver.StaticBroadcastReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="android.intent.action.TIME_SET"/>
<action android:name="android.intent.action.TIMEZONE_CHANGED"/>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_CHANGED"/>
<action android:name="android.intent.action.PACKAGE_DATA_CLEARED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<action android:name="android.intent.action.PACKAGE_RESTARTED"/>
<action android:name="android.intent.action.UID_REMOVED"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
<action android:name="android.intent.action.ACTION_SHUTDOWN"/>
<action android:name="android.intent.action.DATE_CHANGED"/>
<action android:name="android.intent.action.DREAMING_STARTED"/>
<action android:name="android.intent.action.DREAMING_STOPPED"/>
<action android:name="android.intent.action.HEADSET_PLUG"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED"/>
<action android:name="android.intent.action.LOCALE_CHANGED"/>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
<action android:name="android.intent.action.MEDIA_CHECKING"/>
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<action android:name="android.intent.action.PACKAGE_FIRST_LAUNCH"/>
<action android:name="android.intent.action.PROVIDER_CHANGED"/>
<action android:name="android.intent.action.WALLPAPER_CHANGED"/>
<action android:name="android.intent.action.USER_UNLOCKED"/>
<action android:name="android.intent.action.USER_PRESENT"/>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_CHANGED"/>
<action android:name="android.intent.action.PACKAGE_DATA_CLEARED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<action android:name="android.intent.action.PACKAGE_RESTARTED"/>
<data android:scheme="package"/>
</intent-filter>
</receiver>

View File

@@ -1,5 +1,7 @@
package org.autojs.autojs;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -10,29 +12,26 @@ import android.widget.ImageView;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
import com.flurry.android.FlurryAgent;
import com.raizlabs.android.dbflow.config.DatabaseConfig;
import com.raizlabs.android.dbflow.config.FlowConfig;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.runtime.DirectModelNotifier;
import com.squareup.leakcanary.LeakCanary;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.core.ui.inflater.ImageLoader;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.autojs.key.GlobalKeyObserver;
import org.autojs.autojs.network.GlideApp;
import org.autojs.autojs.storage.database.IntentTaskDatabase;
import org.autojs.autojs.storage.database.TimedTaskDatabase;
import org.autojs.autojs.timing.TimedTaskScheduler;
import org.autojs.autojs.tool.CrashHandler;
import org.autojs.autojs.ui.error.ErrorReportActivity;
import com.stardust.theme.ThemeColor;
import com.stardust.theme.ThemeColorManager;
import com.tencent.bugly.crashreport.CrashReport;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.autojs.key.GlobalKeyObserver;
import org.autojs.autojs.external.receiver.DynamicBroadcastReceivers;
import org.autojs.autojs.network.GlideApp;
import org.autojs.autojs.timing.IntentTask;
import org.autojs.autojs.timing.TimedTaskManager;
import org.autojs.autojs.timing.TimedTaskScheduler;
import org.autojs.autojs.tool.CrashHandler;
import org.autojs.autojs.ui.error.ErrorReportActivity;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* Created by Stardust on 2017/1/27.
@@ -44,6 +43,7 @@ public class App extends MultiDexApplication {
private static final String BUGLY_APP_ID = "19b3607b53";
private static WeakReference<App> instance;
private DynamicBroadcastReceivers mDynamicBroadcastReceivers;
public static App getApp() {
return instance.get();
@@ -58,6 +58,10 @@ public class App extends MultiDexApplication {
init();
}
public DynamicBroadcastReceivers getDynamicBroadcastReceivers() {
return mDynamicBroadcastReceivers;
}
private void setUpStaticsTool() {
if (BuildConfig.DEBUG)
return;
@@ -67,6 +71,11 @@ public class App extends MultiDexApplication {
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
private void setUpDebugEnvironment() {
CrashHandler crashHandler = new CrashHandler(ErrorReportActivity.class);
@@ -77,17 +86,16 @@ public class App extends MultiDexApplication {
crashHandler.setBuglyHandler(Thread.getDefaultUncaughtExceptionHandler());
Thread.setDefaultUncaughtExceptionHandler(crashHandler);
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
//LeakCanary.install(this);
}
private void init() {
FlowManager.init(FlowConfig.builder(this)
.addDatabaseConfig(DatabaseConfig.builder(TimedTaskDatabase.class)
.modelNotifier(DirectModelNotifier.get())
.build())
.addDatabaseConfig(DatabaseConfig.builder(IntentTaskDatabase.class)
.modelNotifier(DirectModelNotifier.get())
.build())
.build());
ThemeColorManager.setDefaultThemeColor(new ThemeColor(getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark), getResources().getColor(R.color.colorAccent)));
ThemeColorManager.init(this);
AutoJs.initInstance(this);
@@ -96,6 +104,18 @@ public class App extends MultiDexApplication {
}
setupDrawableImageLoader();
TimedTaskScheduler.checkTasksRepeatedlyIfNeeded(this);
initDynamicBroadcastReceivers();
}
@SuppressLint("CheckResult")
private void initDynamicBroadcastReceivers() {
mDynamicBroadcastReceivers = new DynamicBroadcastReceivers(this);
TimedTaskManager.getInstance().getAllIntentTasks()
.filter(task -> task.getAction() != null)
.map(IntentTask::getAction)
.collectInto(new ArrayList<String>(), ArrayList::add)
.subscribe(list -> mDynamicBroadcastReceivers.register(list),
Throwable::printStackTrace);
}
private void setupDrawableImageLoader() {

View File

@@ -201,4 +201,8 @@ public class Pref {
getString(R.string.default_value_script_dir_path));
return new File(Environment.getExternalStorageDirectory(), dir).getPath();
}
public static boolean isForegroundServiceEnabled() {
return def().getBoolean(getString(R.string.key_foreground_servie), false);
}
}

View File

@@ -50,6 +50,7 @@ public class AutoJsApkBuilder extends ApkBuilder {
public static AppConfig fromProjectConfig(String projectDir, ProjectConfig projectConfig) {
return new AppConfig()
.setAppName(projectConfig.getName())
.setPackageName(projectConfig.getPackageName())
.ignoreDir(new File(projectDir, projectConfig.getBuildDir()))
.setVersionCode(projectConfig.getVersionCode())
.setVersionName(projectConfig.getVersionName())

View File

@@ -0,0 +1,88 @@
package org.autojs.autojs.external.foreground;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import com.stardust.app.GlobalAppContext;
import org.autojs.autojs.R;
import org.autojs.autojs.ui.main.MainActivity_;
public class ForegroundService extends Service {
private static final int NOTIFICATION_ID = 1;
private static final String CHANEL_ID = ForegroundService.class.getName() + ".foreground";
public static void start(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(new Intent(context, ForegroundService.class));
} else {
context.startService(new Intent(context, ForegroundService.class));
}
}
public static void stop(Context context){
context.stopService(new Intent(context, ForegroundService.class));
}
@Override
public void onCreate() {
super.onCreate();
startForeground();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void startForeground() {
startForeground(NOTIFICATION_ID, buildNotification());
}
private Notification buildNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel();
}
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, MainActivity_.intent(this).get(), 0);
return new NotificationCompat.Builder(this, CHANEL_ID)
.setContentTitle(getString(R.string.foreground_notification_title))
.setContentText(getString(R.string.foreground_notification_text))
.setSmallIcon(R.drawable.autojs_material)
.setWhen(System.currentTimeMillis())
.setContentIntent(contentIntent)
.setChannelId(CHANEL_ID)
.build();
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void createNotificationChannel() {
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
CharSequence name = getString(R.string.foreground_notification_channel_name);
String description = getString(R.string.foreground_notification_channel_name);
NotificationChannel channel = new NotificationChannel(CHANEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(description);
channel.enableLights(false);
manager.createNotificationChannel(channel);
}
@Override
public void onDestroy() {
stopForeground(true);
super.onDestroy();
}
}

View File

@@ -1,4 +1,4 @@
package org.autojs.autojs.external.boot;
package org.autojs.autojs.external.receiver;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
@@ -7,6 +7,7 @@ import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.execution.ExecutionConfig;
import org.autojs.autojs.autojs.AutoJs;
@@ -17,23 +18,25 @@ import org.autojs.autojs.timing.TimedTaskManager;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class BootCompleteReceiver extends BroadcastReceiver {
public class BaseBroadcastReceiver extends BroadcastReceiver {
private static final String LOG_TAG = "BootCompleteReceiver";
private static final String LOG_TAG = "BaseBroadcastReceiver";
@SuppressLint("CheckResult")
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Log.i(LOG_TAG, "on boot complete");
Log.d(LOG_TAG, "onReceive: intent = " + intent + ", this = " + this);
try {
TimedTaskManager.getInstance().getIntentTaskOfAction(intent.getAction())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intentTask -> runTask(context, intent, intentTask), Throwable::printStackTrace);
} catch (Exception e) {
GlobalAppContext.toast(e.getMessage());
}
}
private void runTask(Context context, Intent intent, IntentTask task) {
static void runTask(Context context, Intent intent, IntentTask task) {
Log.d(LOG_TAG, "runTask: action = " + intent.getAction() + ", script = " + task.getScriptPath());
ScriptFile file = new ScriptFile(task.getScriptPath());
ExecutionConfig config = new ExecutionConfig();
config.setArgument("intent", intent.clone());
@@ -45,4 +48,5 @@ public class BootCompleteReceiver extends BroadcastReceiver {
Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}

View File

@@ -0,0 +1,78 @@
package org.autojs.autojs.external.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.os.Build;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static android.content.Intent.ACTION_BATTERY_CHANGED;
import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
import static android.content.Intent.ACTION_PACKAGES_SUSPENDED;
import static android.content.Intent.ACTION_PACKAGES_UNSUSPENDED;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.content.Intent.ACTION_TIME_TICK;
public class DynamicBroadcastReceivers {
private static final List<String> DEFAULT_ACTIONS = new ArrayList<>(Arrays.asList(
ACTION_TIME_TICK,
ACTION_SCREEN_OFF,
ACTION_SCREEN_ON,
ACTION_BATTERY_CHANGED,
ACTION_CONFIGURATION_CHANGED
));
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
DEFAULT_ACTIONS.addAll(Arrays.asList(
ACTION_PACKAGES_SUSPENDED,
ACTION_PACKAGES_UNSUSPENDED
));
}
}
private final Set<String> mActions = new LinkedHashSet<>();
private final List<BroadcastReceiver> mReceivers = new ArrayList<>();
private final Context mContext;
public DynamicBroadcastReceivers(Context context) {
mContext = context;
register(DEFAULT_ACTIONS);
}
public void register(String action) {
register(Collections.singletonList(action));
}
public void register(List<String> actions) {
IntentFilter filter = new IntentFilter();
for (String action : actions) {
if (!StaticBroadcastReceiver.ACTIONS.contains(action)
&& !mActions.contains(action)) {
mActions.add(action);
filter.addAction(action);
}
}
if (filter.countActions() == 0) {
return;
}
BaseBroadcastReceiver receiver = new BaseBroadcastReceiver();
mContext.registerReceiver(receiver, filter);
}
public void unregisterAll() {
for (BroadcastReceiver receiver : mReceivers) {
mContext.unregisterReceiver(receiver);
}
mReceivers.clear();
}
}

View File

@@ -0,0 +1,40 @@
package org.autojs.autojs.external.receiver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StaticBroadcastReceiver extends BaseBroadcastReceiver {
static final List<String> ACTIONS = new ArrayList<>(Arrays.asList(
"android.intent.action.BOOT_COMPLETED",
"android.intent.action.QUICKBOOT_POWERON",
"android.intent.action.TIME_SET",
"android.intent.action.TIMEZONE_CHANGED",
"android.intent.action.PACKAGE_ADDED",
"android.intent.action.PACKAGE_CHANGED",
"android.intent.action.PACKAGE_DATA_CLEARED",
"android.intent.action.PACKAGE_REMOVED",
"android.intent.action.PACKAGE_RESTARTED",
"android.intent.action.UID_REMOVED",
"android.intent.action.ACTION_POWER_CONNECTED",
"android.intent.action.ACTION_POWER_DISCONNECTED",
"android.intent.action.ACTION_SHUTDOWN",
"android.intent.action.DATE_CHANGED",
"android.intent.action.DREAMING_STARTED",
"android.intent.action.DREAMING_STOPPED",
"android.intent.action.HEADSET_PLUG",
"android.intent.action.INPUT_METHOD_CHANGED",
"android.intent.action.LOCALE_CHANGED",
"android.intent.action.MEDIA_BUTTON",
"android.intent.action.MEDIA_CHECKING",
"android.intent.action.MEDIA_MOUNTED",
"android.intent.action.PACKAGE_FIRST_LAUNCH",
"android.intent.action.PROVIDER_CHANGED",
"android.intent.action.WALLPAPER_CHANGED",
"android.intent.action.USER_UNLOCKED",
"android.intent.action.USER_PRESENT",
"android.net.conn.CONNECTIVITY_CHANGE"
));
}

View File

@@ -6,6 +6,7 @@ import android.content.Intent;
import android.widget.Toast;
import org.autojs.autojs.R;
import org.autojs.autojs.timing.TaskReceiver;
import org.autojs.autojs.tool.EmptyObservers;
import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.edit.EditorView;
@@ -29,7 +30,8 @@ import static org.autojs.autojs.ui.edit.EditorView.EXTRA_SAVE_ENABLED;
@EActivity(R.layout.activity_tasker_script_edit)
public class TaskerScriptEditActivity extends BaseActivity {
public static final int REQUEST_CODE = "Love you. Can we go back?".hashCode() >> 16;
public static final int REQUEST_CODE = 10016;
public static final String EXTRA_TASK_ID = TaskReceiver.EXTRA_TASK_ID;
public static void edit(Activity activity, String title, String summary, String content) {
activity.startActivityForResult(new Intent(activity, TaskerScriptEditActivity_.class)
@@ -63,4 +65,9 @@ public class TaskerScriptEditActivity extends BaseActivity {
TaskerScriptEditActivity.super.finish();
}
@Override
protected void onDestroy() {
mEditorView.destroy();
super.onDestroy();
}
}

View File

@@ -161,4 +161,7 @@ public class AutoCompletion {
}
public void shutdown(){
mExecutorService.shutdownNow();
}
}

View File

@@ -30,6 +30,10 @@ public class Explorer {
this(explorerProvider, cacheSize, EventBus.getDefault());
}
public ExplorerProvider getProvider() {
return mExplorerProvider;
}
public void notifyChildrenChanged(ExplorerPage page) {
clearCache(page);
mEventBus.post(new ExplorerChangeEvent(page, CHILDREN_CHANGE, null));

View File

@@ -4,16 +4,16 @@ import com.stardust.pio.PFile;
import java.io.File;
public class ExplorerSamleItem extends ExplorerFileItem {
public ExplorerSamleItem(PFile file, ExplorerPage parent) {
public class ExplorerSampleItem extends ExplorerFileItem {
public ExplorerSampleItem(PFile file, ExplorerPage parent) {
super(file, parent);
}
public ExplorerSamleItem(String path, ExplorerPage parent) {
public ExplorerSampleItem(String path, ExplorerPage parent) {
super(path, parent);
}
public ExplorerSamleItem(File file, ExplorerPage parent) {
public ExplorerSampleItem(File file, ExplorerPage parent) {
super(file, parent);
}

View File

@@ -8,6 +8,7 @@ import com.stardust.pio.PFile;
import com.stardust.pio.PFiles;
import org.autojs.autojs.Pref;
import org.autojs.autojs.model.sample.SampleFile;
import java.io.File;
import java.io.FileFilter;
@@ -52,7 +53,7 @@ public class WorkspaceFileProvider extends ExplorerFileProvider {
}
} else {
if (file.getPath().startsWith(mSampleDir.getPath())) {
p.addChild(new ExplorerSamleItem(file, p));
p.addChild(new ExplorerSampleItem(file, p));
} else {
p.addChild(new ExplorerFileItem(file, p));
}
@@ -98,6 +99,10 @@ public class WorkspaceFileProvider extends ExplorerFileProvider {
});
}
public static void resetSample(SampleFile sampleFile){
}
@Override
protected ExplorerDirPage createExplorerPage(String path, ExplorerPage parent) {
ExplorerDirPage page = super.createExplorerPage(path, parent);

View File

@@ -12,10 +12,9 @@ import android.util.Pair;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.stardust.util.MapEntries;
import com.stardust.util.MapBuilder;
import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.tool.EmptyObservers;
import java.io.IOException;
import java.net.SocketTimeoutException;
@@ -24,7 +23,6 @@ import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -171,12 +169,12 @@ public class DevPluginService {
@WorkerThread
private void sayHelloToServer(JsonWebSocket socket) throws IOException {
writeMap(socket, TYPE_HELLO, new MapEntries<String, Object>()
.entry("device_name", Build.BRAND + " " + Build.MODEL)
.entry("client_version", CLIENT_VERSION)
.entry("app_version", BuildConfig.VERSION_NAME)
.entry("app_version_code", BuildConfig.VERSION_CODE)
.map());
writeMap(socket, TYPE_HELLO, new MapBuilder<String, Object>()
.put("device_name", Build.BRAND + " " + Build.MODEL)
.put("client_version", CLIENT_VERSION)
.put("app_version", BuildConfig.VERSION_NAME)
.put("app_version_code", BuildConfig.VERSION_CODE)
.build());
mHandshakeTimeoutHandler.postDelayed(() -> {
if (mSocket != socket && !socket.isClosed()) {
onHandshakeTimeout(socket);

View File

@@ -0,0 +1,27 @@
package org.autojs.autojs.storage.database;
public abstract class BaseModel {
private long mId;
public void setId(long id) {
mId = id;
}
public long getId() {
return mId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BaseModel baseModel = (BaseModel) o;
return mId == baseModel.mId;
}
@Override
public int hashCode() {
return (int)(mId ^ (mId >>> 32));
}
}

View File

@@ -0,0 +1,173 @@
package org.autojs.autojs.storage.database;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import org.autojs.autojs.timing.IntentTask;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
public abstract class Database<M extends BaseModel> {
private final SQLiteDatabase mWritableSQLiteDatabase;
private final SQLiteDatabase mReadableSQLiteDatabase;
private final String mTable;
private final PublishSubject<ModelChange<M>> mModelChange = PublishSubject.create();
public Database(SQLiteOpenHelper sqLiteOpenHelper, String table) {
mWritableSQLiteDatabase = sqLiteOpenHelper.getWritableDatabase();
mReadableSQLiteDatabase = sqLiteOpenHelper.getWritableDatabase();
mTable = table;
}
public <T> Observable<T> exec(Callable<T> callable) {
return Observable.fromCallable(callable)
.subscribeOn(Schedulers.io());
}
public <T> Flowable<T> execFlowable(Callable<T> callable) {
return Flowable.fromCallable(callable)
.subscribeOn(Schedulers.io());
}
public PublishSubject<ModelChange<M>> getModelChange() {
return mModelChange;
}
public Observable<Integer> delete(M model) {
return exec(() -> {
int delete = mWritableSQLiteDatabase.delete(mTable, "id = ?",
new String[]{String.valueOf(model.getId())});
if (delete >= 1) {
mModelChange.onNext(new ModelChange<>(model, ModelChange.DELETE));
}
return delete;
});
}
public Observable<Integer> update(M model) {
return exec(() -> {
ContentValues values = asContentValues(model);
int update = mWritableSQLiteDatabase.update(mTable, values, "id = ?", arg(model.getId()));
if (update >= 1) {
mModelChange.onNext(new ModelChange<>(model, ModelChange.UPDATE));
}
return update;
});
}
public Observable<Long> insert(M model) {
return exec(() -> {
ContentValues values = asContentValues(model);
long id = mWritableSQLiteDatabase.insertOrThrow(mTable, null, values);
if (id >= 0) {
model.setId(id);
mModelChange.onNext(new ModelChange<>(model, ModelChange.INSERT));
}
return id;
});
}
protected abstract M createModelFromCursor(Cursor cursor);
protected abstract ContentValues asContentValues(M model);
public M queryById(long id) {
Cursor cursor = mReadableSQLiteDatabase.rawQuery("SELECT * FROM " + mTable + " WHERE id = ?", arg(id));
if (!cursor.moveToFirst()) {
return null;
}
M model = createModelFromCursor(cursor);
cursor.close();
return model;
}
public Flowable<M> queryAllAsFlowable() {
return execFlowable(() ->
mReadableSQLiteDatabase.rawQuery("SELECT * FROM " + mTable, null)
)
.flatMap(cursor -> Flowable.fromIterable(() -> new CursorIterator(cursor)))
.map(this::createModelFromCursor);
}
public List<M> queryAll() {
ArrayList<M> list = new ArrayList<>();
Cursor cursor = mReadableSQLiteDatabase.rawQuery("SELECT * FROM " + mTable, null);
while (cursor.moveToNext()) {
list.add(createModelFromCursor(cursor));
}
cursor.close();
return list;
}
public long count() {
Cursor cursor = mReadableSQLiteDatabase.rawQuery("SELECT COUNT(*) FROM " + mTable, null);
if (cursor.moveToFirst()) {
return cursor.getLong(0);
}
cursor.close();
return 0;
}
public Flowable<M> query(String sql, Object... args) {
String[] strArgs = args(args);
return execFlowable(() ->
mReadableSQLiteDatabase.query(mTable, null, sql, strArgs, null, null, null)
)
.flatMap(cursor -> Flowable.fromIterable(() -> new CursorIterator(cursor)))
.map(this::createModelFromCursor);
}
private String[] args(Object[] args) {
if (args == null || args.length == 0) {
return null;
}
String[] a = new String[args.length];
for (int i = 0; i < args.length; i++) {
a[i] = String.valueOf(args[i]);
}
return a;
}
private String[] arg(Object value) {
return new String[]{String.valueOf(value)};
}
private static class CursorIterator implements Iterator<Cursor> {
private final Cursor mCursor;
private CursorIterator(Cursor cursor) {
mCursor = cursor;
}
@Override
public boolean hasNext() {
boolean next = mCursor.moveToNext();
if (!next) {
mCursor.close();
}
return next;
}
@Override
public Cursor next() {
return mCursor;
}
}
}

View File

@@ -1,7 +1,64 @@
package org.autojs.autojs.storage.database;
import com.raizlabs.android.dbflow.annotation.Database;
@Database(version = 1)
public class IntentTaskDatabase {
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import org.autojs.autojs.timing.IntentTask;
public class IntentTaskDatabase extends Database<IntentTask> {
private static final int VERSION = 1;
private static final String NAME = "IntentTaskDatabase";
public IntentTaskDatabase(Context context){
super(new SQLHelper(context), IntentTask.TABLE);
}
@Override
protected ContentValues asContentValues(IntentTask model) {
ContentValues values = new ContentValues();
values.put("script_path", model.getScriptPath());
values.put("action", model.getAction());
values.put("category", model.getCategory());
values.put("data_type", model.getDataType());
return values;
}
@Override
protected IntentTask createModelFromCursor(Cursor cursor) {
IntentTask task = new IntentTask();
task.setId(cursor.getInt(0));
task.setScriptPath(cursor.getString(1));
task.setAction(cursor.getString(2));
task.setCategory(cursor.getString(3));
task.setDataType(cursor.getString(4));
return task;
}
private static class SQLHelper extends SQLiteOpenHelper {
public SQLHelper(Context context) {
super(context, NAME + ".db", null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE `" + IntentTask.TABLE + "`(" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT, " +
"`script_path` TEXT NOT NULL ON CONFLICT FAIL, " +
"`action` TEXT, " +
"`category` TEXT, " +
"`data_type` TEXT);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}

View File

@@ -1,6 +1,5 @@
package org.autojs.autojs.storage.database;
import com.raizlabs.android.dbflow.structure.BaseModel;
/**
* Created by Stardust on 2017/11/28.
@@ -8,10 +7,15 @@ import com.raizlabs.android.dbflow.structure.BaseModel;
public class ModelChange<M> {
private final M mData;
private final BaseModel.Action mAction;
public static final int INSERT = 1;
public static final int UPDATE = 2;
public static final int DELETE = 3;
public ModelChange(M data, BaseModel.Action action) {
private final M mData;
private final int mAction;
public ModelChange(M data, int action) {
mData = data;
mAction = action;
}
@@ -20,7 +24,7 @@ public class ModelChange<M> {
return mData;
}
public BaseModel.Action getAction() {
public int getAction() {
return mAction;
}

View File

@@ -1,12 +1,74 @@
package org.autojs.autojs.storage.database;
import com.raizlabs.android.dbflow.annotation.Database;
/**
* Created by Stardust on 2017/11/28.
*/
@Database(version = 1)
public class TimedTaskDatabase {
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import org.autojs.autojs.timing.TimedTask;
public class TimedTaskDatabase extends Database<TimedTask> {
private static final int VERSION = 3;
private static final String NAME = "TimedTaskDatabase";
public TimedTaskDatabase(Context context) {
super(new SQLHelper(context), TimedTask.TABLE);
}
@Override
protected ContentValues asContentValues(TimedTask model) {
ContentValues values = new ContentValues();
values.put("time", model.getTimeFlag());
values.put("scheduled", model.isScheduled());
values.put("delay", model.getDelay());
values.put("interval", model.getInterval());
values.put("loop_times", model.getLoopTimes());
values.put("millis", model.getMillis());
values.put("script_path", model.getScriptPath());
return values;
}
@Override
protected TimedTask createModelFromCursor(Cursor cursor) {
TimedTask task = new TimedTask();
task.setId(cursor.getInt(0));
task.setTimeFlag(cursor.getLong(1));
task.setScheduled(cursor.getInt(2) != 0);
task.setDelay(cursor.getLong(3));
task.setInterval(cursor.getLong(4));
task.setLoopTimes(cursor.getInt(5));
task.setMillis(cursor.getLong(6));
task.setScriptPath(cursor.getString(7));
return task;
}
private static class SQLHelper extends SQLiteOpenHelper {
public SQLHelper(Context context) {
super(context, NAME + ".db", null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE `" + TimedTask.TABLE + "`(" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT, " +
"`time` INTEGER, " +
"`scheduled` INTEGER, " +
"`delay` INTEGER, " +
"`interval` INTEGER, " +
"`loop_times` INTEGER, " +
"`millis` INTEGER, " +
"`script_path` TEXT);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}

View File

@@ -2,33 +2,19 @@ package org.autojs.autojs.timing;
import android.content.IntentFilter;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.NotNull;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import org.autojs.autojs.storage.database.BaseModel;
import org.autojs.autojs.storage.database.IntentTaskDatabase;
import org.autojs.autojs.storage.database.TimedTaskDatabase;
public class IntentTask extends BaseModel {
@Table(database = IntentTaskDatabase.class)
public class IntentTask {
public static final String TABLE = "IntentTask";
@PrimaryKey(autoincrement = true, quickCheckAutoIncrement = true)
@Column(name = "id")
int mId = -1;
private String mScriptPath;
@NotNull
@Column(name = "script_path")
String mScriptPath;
private String mAction;
@Column(name = "action")
String mAction;
private String mCategory;
@Column(name = "category")
String mCategory;
@Column(name = "data_type")
String mDataType;
private String mDataType;
public IntentFilter getIntentFilter() {
IntentFilter filter = new IntentFilter();
@@ -48,14 +34,6 @@ public class IntentTask {
return filter;
}
public int getId() {
return mId;
}
public void setId(int id) {
mId = id;
}
public String getScriptPath() {
return mScriptPath;
}

View File

@@ -20,7 +20,7 @@ public class TaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ScriptIntents.handleIntent(context, intent);
int id = intent.getIntExtra(EXTRA_TASK_ID, -1);
long id = intent.getLongExtra(EXTRA_TASK_ID, -1);
if (id >= 0) {
TimedTaskManager.getInstance().notifyTaskFinished(id);
}

View File

@@ -4,13 +4,10 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.stardust.autojs.execution.ExecutionConfig;
import org.autojs.autojs.external.ScriptIntents;
import org.autojs.autojs.storage.database.TimedTaskDatabase;
import org.autojs.autojs.external.ScriptIntents;
import org.autojs.autojs.storage.database.BaseModel;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.LocalDateTime;
@@ -19,11 +16,9 @@ import org.joda.time.LocalTime;
import java.util.concurrent.TimeUnit;
/**
* Created by Stardust on 2017/11/27.
*/
@Table(database = TimedTaskDatabase.class)
public class TimedTask {
public class TimedTask extends BaseModel {
public static final String TABLE = "TimedTask";
private static final int FLAG_DISPOSABLE = 0;
public final static int FLAG_SUNDAY = 0x1;
@@ -36,29 +31,18 @@ public class TimedTask {
private static final int FLAG_EVERYDAY = 0x7F;
private static final int REQUEST_CODE = 2000;
@PrimaryKey(autoincrement = true, quickCheckAutoIncrement = true)
@Column(name = "id")
int mId = -1;
@Column(name = "time")
long mTimeFlag;
@Column(name = "scheduled")
boolean mScheduled;
@Column(name = "delay")
long mDelay = 0;
@Column(name = "interval")
long mInterval = 0;
@Column(name = "loop_times")
int mLoopTimes = 1;
@Column(name = "millis")
long mMillis;
@Column(name = "script_path")
String mScriptPath;
public TimedTask() {
@@ -148,19 +132,10 @@ public class TimedTask {
return mMillis;
}
public int getId() {
return mId;
}
public String getScriptPath() {
return mScriptPath;
}
public void setId(int id) {
mId = id;
}
public long getTimeFlag() {
return mTimeFlag;
}
@@ -207,7 +182,7 @@ public class TimedTask {
public Intent createIntent() {
return new Intent(TaskReceiver.ACTION_TASK)
.putExtra(TaskReceiver.EXTRA_TASK_ID, mId)
.putExtra(TaskReceiver.EXTRA_TASK_ID, getId())
.putExtra(ScriptIntents.EXTRA_KEY_PATH, mScriptPath)
.putExtra(ScriptIntents.EXTRA_KEY_DELAY, mDelay)
.putExtra(ScriptIntents.EXTRA_KEY_LOOP_TIMES, mLoopTimes)
@@ -216,14 +191,14 @@ public class TimedTask {
public PendingIntent createPendingIntent(Context context) {
return PendingIntent.getBroadcast(context, REQUEST_CODE + 1 + getId(),
return PendingIntent.getBroadcast(context, (int) ((REQUEST_CODE + 1 + getId()) % 65535),
createIntent(), PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public String toString() {
return "TimedTask{" +
"mId=" + mId +
"mId=" + getId() +
", mTimeFlag=" + mTimeFlag +
", mScheduled=" + mScheduled +
", mDelay=" + mDelay +
@@ -234,20 +209,6 @@ public class TimedTask {
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimedTask timedTask = (TimedTask) o;
return mId == timedTask.mId;
}
@Override
public int hashCode() {
return mId;
}
public static TimedTask dailyTask(LocalTime time, String scriptPath, ExecutionConfig config) {
return new TimedTask(time.getMillisOfDay(), FLAG_EVERYDAY, scriptPath, config);

View File

@@ -1,29 +1,22 @@
package org.autojs.autojs.timing;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.runtime.DirectModelNotifier;
import com.raizlabs.android.dbflow.rx2.language.RXSQLite;
import com.raizlabs.android.dbflow.sql.language.SQLite;
import com.raizlabs.android.dbflow.structure.BaseModel;
import com.raizlabs.android.dbflow.structure.ModelAdapter;
import com.stardust.app.GlobalAppContext;
import org.autojs.autojs.Pref;
import org.autojs.autojs.App;
import org.autojs.autojs.storage.database.IntentTaskDatabase;
import org.autojs.autojs.storage.database.ModelChange;
import org.autojs.autojs.storage.database.TimedTaskDatabase;
import org.autojs.autojs.tool.EmptyObservers;
import java.io.File;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
/**
* Created by Stardust on 2017/11/27.
@@ -31,12 +24,10 @@ import io.reactivex.subjects.PublishSubject;
//TODO rx
public class TimedTaskManager {
private static TimedTaskManager sInstance;
private ModelAdapter<TimedTask> mTimedTaskModelAdapter;
private ModelAdapter<IntentTask> mIntentTaskModelAdapter;
private Context mContext;
private PublishSubject<ModelChange<TimedTask>> mTimedTaskChanges = PublishSubject.create();
private TimedTaskDatabase mTimedTaskDatabase;
private IntentTaskDatabase mIntentTaskDatabase;
public static TimedTaskManager getInstance() {
if (sInstance == null) {
@@ -45,104 +36,132 @@ public class TimedTaskManager {
return sInstance;
}
@SuppressLint("CheckResult")
public TimedTaskManager(Context context) {
mContext = context;
mTimedTaskModelAdapter = FlowManager.getModelAdapter(TimedTask.class);
mIntentTaskModelAdapter = FlowManager.getModelAdapter(IntentTask.class);
DirectModelNotifier.get().registerForModelChanges(TimedTask.class, new DirectModelNotifier.ModelChangedListener<TimedTask>() {
@Override
public void onModelChanged(@NonNull TimedTask model, @NonNull BaseModel.Action action) {
mTimedTaskChanges.onNext(new ModelChange<>(model, action));
if (action == BaseModel.Action.DELETE && countTasks() == 0) {
TimedTaskScheduler.stopRtcRepeating(mContext);
} else if (action == BaseModel.Action.INSERT) {
TimedTaskScheduler.checkTasksRepeatedlyIfNeeded(mContext);
}
}
@Override
public void onTableChanged(@Nullable Class<?> tableChanged, @NonNull BaseModel.Action action) {
mTimedTaskDatabase = new TimedTaskDatabase(context);
mIntentTaskDatabase = new IntentTaskDatabase(context);
mTimedTaskDatabase.getModelChange().subscribe(change -> {
int action = change.getAction();
if (action == ModelChange.DELETE && countTasks() == 0) {
TimedTaskScheduler.stopRtcRepeating(mContext);
} else if (action == ModelChange.INSERT) {
TimedTaskScheduler.checkTasksRepeatedlyIfNeeded(mContext);
}
});
}
public void notifyTaskFinished(int id) {
@SuppressLint("CheckResult")
public void notifyTaskFinished(long id) {
TimedTask task = getTimedTask(id);
if (task == null)
return;
if (task.isDisposable()) {
mTimedTaskModelAdapter.delete(task);
mTimedTaskDatabase.delete(task)
.subscribe(EmptyObservers.consumer(), Throwable::printStackTrace);
} else {
task.setScheduled(false);
mTimedTaskModelAdapter.update(task);
mTimedTaskDatabase.update(task)
.subscribe(EmptyObservers.consumer(), Throwable::printStackTrace);
}
}
@SuppressLint("CheckResult")
public void removeTask(TimedTask timedTask) {
TimedTaskScheduler.cancel(mContext, timedTask);
mTimedTaskModelAdapter.delete(timedTask);
mTimedTaskDatabase.delete(timedTask)
.subscribe(EmptyObservers.consumer(), Throwable::printStackTrace);
}
@SuppressLint("CheckResult")
public void addTask(TimedTask timedTask) {
mTimedTaskModelAdapter.insert(timedTask);
mTimedTaskDatabase.insert(timedTask)
.subscribe(EmptyObservers.consumer(), Throwable::printStackTrace);;
TimedTaskScheduler.scheduleTaskIfNeeded(mContext, timedTask);
}
@SuppressLint("CheckResult")
public void addTask(IntentTask intentTask) {
mIntentTaskModelAdapter.insert(intentTask);
mIntentTaskDatabase.insert(intentTask)
.subscribe(i -> {
if(!TextUtils.isEmpty(intentTask.getAction())){
App.getApp().getDynamicBroadcastReceivers()
.register(intentTask.getAction());
}
}, Throwable::printStackTrace);
}
@SuppressLint("CheckResult")
public void removeTask(IntentTask intentTask) {
mIntentTaskModelAdapter.delete(intentTask);
mIntentTaskDatabase.delete(intentTask)
.subscribe(EmptyObservers.consumer(), Throwable::printStackTrace);;
}
public Flowable<TimedTask> getAllTasks() {
return RXSQLite.rx(SQLite.select().from(TimedTask.class))
.queryStreamResults()
.subscribeOn(Schedulers.io());
return mTimedTaskDatabase.queryAllAsFlowable();
}
public Flowable<IntentTask> getIntentTaskOfAction(String action) {
IntentTask intentTask = new IntentTask();
intentTask.setAction(Intent.ACTION_BOOT_COMPLETED);
intentTask.setScriptPath(new File(Pref.getScriptDirPath(), "boot.js").getPath());
return Flowable.just(intentTask);
// return RXSQLite.rx(SQLite.select().from(IntentTask.class))
// .queryStreamResults()
// .subscribeOn(Schedulers.io());
return mIntentTaskDatabase.query("action = ?", action);
}
public Observable<ModelChange<TimedTask>> getTimeTaskChanges() {
return mTimedTaskChanges;
return mTimedTaskDatabase.getModelChange();
}
@SuppressLint("CheckResult")
public void notifyTaskScheduled(TimedTask timedTask) {
timedTask.setScheduled(true);
mTimedTaskModelAdapter.update(timedTask);
mTimedTaskDatabase.update(timedTask)
.subscribe(EmptyObservers.consumer(), Throwable::printStackTrace);
}
public List<TimedTask> getAllTasksAsList() {
return SQLite.select().from(TimedTask.class)
.queryList();
return mTimedTaskDatabase.queryAll();
}
public TimedTask getTimedTask(int taskId) {
return SQLite.select()
.from(TimedTask.class)
.where(TimedTask_Table.id.is(taskId))
.querySingle();
public TimedTask getTimedTask(long taskId) {
return mTimedTaskDatabase.queryById(taskId);
}
@SuppressLint("CheckResult")
public void updateTask(TimedTask task) {
mTimedTaskModelAdapter.update(task);
mTimedTaskDatabase.update(task)
.subscribe(EmptyObservers.consumer(), Throwable::printStackTrace);
TimedTaskScheduler.cancel(mContext, task);
TimedTaskScheduler.scheduleTaskIfNeeded(mContext, task);
}
@SuppressLint("CheckResult")
public void updateTask(IntentTask task) {
mIntentTaskDatabase.update(task)
.subscribe(i -> {
if(i > 0 && !TextUtils.isEmpty(task.getAction())){
App.getApp().getDynamicBroadcastReceivers()
.register(task.getAction());
}
}, Throwable::printStackTrace);
}
public long countTasks() {
return SQLite.select().from(TimedTask.class).count();
return mTimedTaskDatabase.count();
}
public List<IntentTask> getAllIntentTasksAsList() {
return mIntentTaskDatabase.queryAll();
}
public Observable<ModelChange<IntentTask>> getIntentTaskChanges() {
return mIntentTaskDatabase.getModelChange();
}
public IntentTask getIntentTask(long intentTaskId) {
return mIntentTaskDatabase.queryById(intentTaskId);
}
public Flowable<IntentTask> getAllIntentTasks() {
return mIntentTaskDatabase.queryAllAsFlowable();
}
}

View File

@@ -28,7 +28,6 @@ public class TimedTaskScheduler extends BroadcastReceiver {
private static final long INTERVAL = TimeUnit.MINUTES.toMillis(1);
private static final long ONE_HOUR = TimeUnit.HOURS.toMillis(1);
private static PendingIntent sCheckTasksPendingIntent;
private static long mNextRtcWakeupMillis = -1;
@Override
public void onReceive(Context context, Intent intent) {
@@ -38,7 +37,7 @@ public class TimedTaskScheduler extends BroadcastReceiver {
}
@SuppressLint("CheckResult")
private static void checkTasks(Context context) {
public static void checkTasks(Context context) {
TimedTaskManager.getInstance().getAllTasks()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -83,7 +82,7 @@ public class TimedTaskScheduler extends BroadcastReceiver {
public static void checkTasksRepeatedlyIfNeeded(Context context) {
if (TimedTaskManager.getInstance().countTasks() > 0 && mNextRtcWakeupMillis > 0) {
if (TimedTaskManager.getInstance().countTasks() > 0) {
setupNextRtcWakeup(context, System.currentTimeMillis() + 5000);
}
}
@@ -95,7 +94,6 @@ public class TimedTaskScheduler extends BroadcastReceiver {
}
AlarmManager alarmManager = getAlarmManager(context);
setExactCompat(alarmManager, createTaskCheckPendingIntent(context), millis);
mNextRtcWakeupMillis = millis;
}

View File

@@ -22,6 +22,7 @@ import com.stardust.app.GlobalAppContext;
import com.stardust.pio.PFile;
import com.stardust.pio.PFiles;
import com.stardust.pio.UncheckedIOException;
import com.tencent.bugly.crashreport.BuglyLog;
import org.autojs.autojs.Pref;
import org.autojs.autojs.R;
@@ -122,7 +123,11 @@ public class ScriptOperations {
}
private void notifyFileCreated(ScriptFile directory, ScriptFile scriptFile) {
mExplorer.notifyItemCreated(new ExplorerFileItem(scriptFile, mExplorerPage));
if (scriptFile.isDirectory()) {
mExplorer.notifyItemCreated(new ExplorerDirPage(scriptFile, mExplorerPage));
} else {
mExplorer.notifyItemCreated(new ExplorerFileItem(scriptFile, mExplorerPage));
}
}
public void newFile() {
@@ -315,9 +320,11 @@ public class ScriptOperations {
public Observable<ScriptFile> download(String url) {
BuglyLog.i(LOG_TAG, "dir = " + Pref.getScriptDirPath() + ", sdcard = " + Environment.getExternalStorageDirectory() + ", url = " + url);
String fileName = DownloadManager.parseFileNameLocally(url);
return new FileChooserDialogBuilder(mContext)
.title(R.string.text_select_save_path)
.dir(Pref.getScriptDirPath())
.chooseDir()
.singleChoice()
.map(saveDir -> new File(saveDir, fileName).getPath())

View File

@@ -74,23 +74,12 @@ public class DocsFragment extends ViewPagerFragment implements BackPressedHandle
}
@Override
public void onResume() {
super.onResume();
((BackPressedHandler.HostActivity) getActivity())
.getBackPressedObserver()
.registerHandlerAtFront(this);
}
@Override
public void onPause() {
super.onPause();
Bundle savedWebViewState = new Bundle();
mWebView.saveState(savedWebViewState);
getArguments().putBundle("savedWebViewState", savedWebViewState);
((BackPressedHandler.HostActivity) getActivity())
.getBackPressedObserver()
.unregisterHandler(this);
}
@Override

View File

@@ -180,6 +180,7 @@ EditActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHo
@Override
protected void onDestroy() {
mEditorView.destroy();
super.onDestroy();
}

View File

@@ -392,7 +392,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show();
}
ScriptExecution execution = Scripts.runWithBroadcastSender(mFile);
if(execution == null){
if (execution == null) {
return null;
}
mScriptExecutionId = execution.getId();
@@ -671,4 +671,9 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
super.onRestoreInstanceState(superData);
setMenuItemStatus(R.id.run, mScriptExecutionId == ScriptExecution.NO_ID);
}
public void destroy() {
mEditor.destroy();
mAutoCompletion.shutdown();
}
}

View File

@@ -373,6 +373,11 @@ public class CodeEditor extends HVScrollView {
mCodeEditText.removeAllBreakpoints();
}
public void destroy(){
mJavaScriptHighlighter.shutdown();
mJsBeautifier.shutdown();
}
@Override
protected void onDraw(Canvas canvas) {
int codeWidth = getWidth() - getPaddingLeft() - getPaddingRight();

View File

@@ -2,10 +2,12 @@ package org.autojs.autojs.ui.edit.editor;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.TimingLogger;
import com.stardust.autojs.rhino.TokenStream;
import com.stardust.pio.UncheckedIOException;
import org.autojs.autojs.ui.edit.theme.Theme;
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
@@ -46,7 +48,7 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
@Override
public String toString() {
return super.toString() + "{count = " + mCount + ", length = " + mText.length() + "}";
return super.toString() + "{count = " + mCount + ", length = " + mText.length() + "}";
}
public int getCharCount() {
@@ -60,16 +62,18 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
private Theme mTheme;
private CodeEditText mCodeEditText;
private ThreadPoolExecutor mExecutorService = new ThreadPoolExecutor(3, 6,
private ThreadPoolExecutor mExecutorService = new ThreadPoolExecutor(3, 6,
2L, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
private AtomicInteger mRunningHighlighterId = new AtomicInteger();
private TimingLogger mLogger = new TimingLogger(CodeEditText.LOG_TAG, "highlight");
private final TextWatcher mTextWatcher;
public JavaScriptHighlighter(Theme theme, CodeEditText codeEditText) {
mExecutorService.allowCoreThreadTimeOut(true);
mTheme = theme;
mCodeEditText = codeEditText;
codeEditText.addTextChangedListener(new SimpleTextWatcher(this));
mTextWatcher = new SimpleTextWatcher(this);
codeEditText.addTextChangedListener(mTextWatcher);
}
@Override
@@ -112,5 +116,9 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
mCodeEditText.updateHighlightTokens(highlightTokens);
}
public void shutdown() {
mCodeEditText.removeTextChangedListener(mTextWatcher);
mExecutorService.shutdownNow();
}
}

View File

@@ -1,290 +0,0 @@
package org.autojs.autojs.ui.edit.editor;
/*
* Copyright 2016. SHENQINCI(沈钦赐)<946736079@qq.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import java.util.Stack;
/**
* 撤销和恢复撤销
* Created by 沈钦赐 on 16/6/23.
*/
public class TextViewRedoUndoer {
//操作序号(一次编辑可能对应多个操作,如替换文字,就是删除+插入)
int index;
//撤销栈
Stack<Action> history = new Stack<>();
//恢复栈
Stack<Action> historyBack = new Stack<>();
private Editable editable;
private EditText editText;
//自动操作标志,防止重复回调,导致无限撤销
private boolean flag = false;
private int mInitialHistoryStackSize;
private boolean mEnabled = true;
public TextViewRedoUndoer(@NonNull EditText editText) {
this.editable = editText.getText();
this.editText = editText;
editText.addTextChangedListener(new Watcher());
}
protected void onEditableChanged(Editable s) {
}
protected void onTextChanged(Editable s) {
if (history.size() < mInitialHistoryStackSize) {
mInitialHistoryStackSize = 0;
}
}
/**
* 清理记录
* Clear history.
*/
public final void clearHistory() {
history.clear();
historyBack.clear();
mInitialHistoryStackSize = 0;
}
public boolean canUndo() {
return !history.empty();
}
/**
* 撤销
* Undo.
*/
public final void undo() {
if (history.empty()) return;
//锁定操作
flag = true;
Action action = history.pop();
historyBack.push(action);
if (action.isAdd) {
//撤销添加
editable.delete(action.startCursor, action.startCursor + action.actionTarget.length());
editText.setSelection(action.startCursor, action.startCursor);
} else {
//插销删除
editable.insert(action.startCursor, action.actionTarget);
if (action.endCursor == action.startCursor) {
editText.setSelection(action.startCursor + action.actionTarget.length());
} else {
editText.setSelection(action.startCursor, action.endCursor);
}
}
//释放操作
flag = false;
//判断是否是下一个动作是否和本动作是同一个操作,直到不同为止
if (!history.empty() && history.peek().index == action.index) {
undo();
}
}
public boolean canRedo() {
return !historyBack.empty();
}
/**
* 恢复
* Redo.
*/
public final void redo() {
if (historyBack.empty()) return;
flag = true;
Action action = historyBack.pop();
history.push(action);
if (action.isAdd) {
//恢复添加
editable.insert(action.startCursor, action.actionTarget);
if (action.endCursor == action.startCursor) {
editText.setSelection(action.startCursor + action.actionTarget.length());
} else {
editText.setSelection(action.startCursor, action.endCursor);
}
} else {
//恢复删除
editable.delete(action.startCursor, action.startCursor + action.actionTarget.length());
editText.setSelection(action.startCursor, action.startCursor);
}
flag = false;
//判断是否是下一个动作是否和本动作是同一个操作
if (!historyBack.empty() && historyBack.peek().index == action.index)
redo();
}
/**
* 首次设置文本
* Set default text.
*/
public final void setDefaultText(CharSequence text) {
clearHistory();
flag = true;
editable.replace(0, editable.length(), text);
flag = false;
}
public boolean isTextChanged() {
return history.size() != mInitialHistoryStackSize;
}
public void markTextAsUnchanged() {
mInitialHistoryStackSize = history.size();
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
public boolean isEnabled() {
return mEnabled;
}
private class Watcher implements TextWatcher {
/**
* Before text changed.
*
* @param s the s
* @param start the start 起始光标
* @param count the endCursor 选择数量
* @param after the after 替换增加的文字数
*/
@Override
public final void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (!editText.isEnabled() || !mEnabled) {
return;
}
if (flag) return;
int end = start + count;
if (end > start && end <= s.length()) {
CharSequence charSequence = s.subSequence(start, end);
//删除了文字
if (charSequence.length() > 0) {
Action action = new Action(charSequence, start, false);
if (count > 1) {
//如果一次超过一个字符,说名用户选择了,然后替换或者删除操作
action.setSelectCount(count);
} else if (count == 1 && count == after) {
//一个字符替换
action.setSelectCount(count);
}
//还有一种情况:选择一个字符,然后删除(暂时没有考虑这种情况)
history.push(action);
historyBack.clear();
action.setIndex(++index);
}
}
}
/**
* On text changed.
*
* @param s the s
* @param start the start 起始光标
* @param before the before 选择数量
* @param count the endCursor 添加的数量
*/
@Override
public final void onTextChanged(CharSequence s, int start, int before, int count) {
if (!editText.isEnabled() || !mEnabled) {
return;
}
if (flag) return;
int end = start + count;
if (end > start) {
CharSequence charSequence = s.subSequence(start, end);
//添加文字
if (charSequence.length() > 0) {
Action action = new Action(charSequence, start, true);
history.push(action);
historyBack.clear();
if (before > 0) {
//文字替换(先删除再增加),删除和增加是同一个操作,所以不需要增加序号
action.setIndex(index);
} else {
action.setIndex(++index);
}
}
}
}
@Override
public final void afterTextChanged(Editable s) {
if (!editText.isEnabled() || !mEnabled) {
return;
}
if (flag) return;
if (s != editable) {
editable = s;
onEditableChanged(s);
}
TextViewRedoUndoer.this.onTextChanged(s);
}
}
private class Action {
/**
* 改变字符.
*/
CharSequence actionTarget;
/**
* 光标位置.
*/
int startCursor;
int endCursor;
/**
* 标志增加操作.
*/
boolean isAdd;
/**
* 操作序号.
*/
int index;
public Action(CharSequence actionTag, int startCursor, boolean add) {
this.actionTarget = actionTag;
this.startCursor = startCursor;
this.endCursor = startCursor;
this.isAdd = add;
}
public void setSelectCount(int count) {
this.endCursor = endCursor + count;
}
public void setIndex(int index) {
this.index = index;
}
}
}

View File

@@ -9,6 +9,7 @@ import java.util.LinkedList;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Handler;
import android.text.Editable;
import android.text.Selection;
import android.text.TextWatcher;
@@ -46,6 +47,10 @@ public class TextViewUndoRedo {
private int mInitialHistoryStackSize;
private Handler mHandler = new Handler();
private int mTextChangeId = 0;
private boolean mTextChanging = false;
// =================================================================== //
/**
@@ -404,7 +409,6 @@ public class TextViewUndoRedo {
if (mIsUndoOrRedo || !mEnabled) {
return;
}
mBeforeChange = s.subSequence(start, start + count);
}
@@ -415,7 +419,10 @@ public class TextViewUndoRedo {
}
mAfterChange = s.subSequence(start, start + count);
mTextChangeId++;
mEditHistory.add(new EditItem(start, mBeforeChange, mAfterChange));
int textChangeId = mTextChangeId;
//TODO 增加连续输入文字当成一次撤销的功能
}
public void afterTextChanged(Editable s) {

View File

@@ -4,9 +4,6 @@ import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.CardView;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -14,7 +11,6 @@ import com.stardust.autojs.project.ProjectConfig;
import com.stardust.autojs.project.ProjectLauncher;
import com.stardust.pio.PFile;
import org.androidannotations.annotations.Click;
import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.model.explorer.ExplorerChangeEvent;
@@ -59,15 +55,19 @@ public class ExplorerProjectToolbar extends CardView {
setOnClickListener(view -> edit());
}
public void setProject(PFile dir, ProjectConfig config) {
public void setProject(PFile dir) {
mProjectConfig = ProjectConfig.fromProjectDir(dir.getPath());
if(mProjectConfig == null){
setVisibility(GONE);
return;
}
mDirectory = dir;
mProjectConfig = config;
mProjectName.setText(config.getName());
mProjectName.setText(mProjectConfig.getName());
}
public void refresh() {
if (mDirectory != null) {
setProject(mDirectory, ProjectConfig.fromProjectDir(mDirectory.getPath()));
setProject(mDirectory);
}
}

View File

@@ -27,7 +27,11 @@ import org.autojs.autojs.model.explorer.ExplorerFileItem;
import org.autojs.autojs.model.explorer.ExplorerItem;
import org.autojs.autojs.model.explorer.ExplorerPage;
import org.autojs.autojs.model.explorer.ExplorerProjectPage;
import org.autojs.autojs.model.explorer.ExplorerSampleItem;
import org.autojs.autojs.model.explorer.ExplorerSamplePage;
import org.autojs.autojs.model.explorer.Explorers;
import org.autojs.autojs.model.explorer.WorkspaceFileProvider;
import org.autojs.autojs.model.sample.SampleFile;
import org.autojs.autojs.model.script.ScriptFile;
import org.autojs.autojs.model.script.Scripts;
import org.autojs.autojs.ui.project.BuildActivity;
@@ -111,7 +115,7 @@ public class ExplorerView extends ThemeColorSwipeRefreshLayout implements SwipeR
mCurrentPageState = currentPageState;
if (mCurrentPageState.page instanceof ExplorerProjectPage) {
mProjectToolbar.setVisibility(VISIBLE);
mProjectToolbar.setProject(currentPageState.page.toScriptFile(), ((ExplorerProjectPage) currentPageState.page).getProjectConfig());
mProjectToolbar.setProject(currentPageState.page.toScriptFile());
} else {
mProjectToolbar.setVisibility(GONE);
}
@@ -306,6 +310,8 @@ public class ExplorerView extends ThemeColorSwipeRefreshLayout implements SwipeR
case R.id.action_sort_by_size:
sort(ExplorerItemList.SORT_TYPE_SIZE, mDirSortMenuShowing);
break;
case R.id.reset:
// WorkspaceFileProvider.resetSample(mSelectedItem.toScriptFile());
default:
return false;
}
@@ -487,6 +493,9 @@ public class ExplorerView extends ThemeColorSwipeRefreshLayout implements SwipeR
}
if (!mExplorerItem.canRename()) {
menu.removeItem(R.id.rename);
}
if(mExplorerItem instanceof ExplorerSampleItem){
}
popupMenu.setOnMenuItemClickListener(ExplorerView.this);
popupMenu.show();

View File

@@ -6,6 +6,7 @@ import android.support.annotation.StringRes;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.pio.PFile;
import com.tencent.bugly.crashreport.BuglyLog;
import org.autojs.autojs.R;
import org.autojs.autojs.model.explorer.Explorer;
@@ -66,7 +67,6 @@ public class FileChooserDialogBuilder extends ThemeColorMaterialDialogBuilder {
public FileChooserDialogBuilder dir(String rootDir, String initialDir) {
mRootDir = rootDir;
mInitialDir = initialDir;
return this;
}

View File

@@ -115,7 +115,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
@Override
public View inflateActionView(FloatyService service, CircularMenuWindow window) {
View actionView = View.inflate(service, R.layout.circular_action_view, null);
mActionViewIcon = (RoundedImageView) actionView.findViewById(R.id.icon);
mActionViewIcon = actionView.findViewById(R.id.icon);
return actionView;
}

View File

@@ -1,11 +1,8 @@
package org.autojs.autojs.ui.floating;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.util.Log;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.WindowManager;
@@ -15,9 +12,13 @@ import com.stardust.enhancedfloaty.FloatyWindow;
import com.stardust.enhancedfloaty.WindowBridge;
import com.stardust.floatingcircularactionmenu.CircularActionMenu;
import com.stardust.floatingcircularactionmenu.gesture.BounceDragGesture;
import com.stardust.util.ScreenMetrics;
public class CircularMenuWindow implements FloatyWindow {
private static final String KEY_POSITION_X = CircularMenuWindow.class.getName() + ".position.x";
private static final String KEY_POSITION_Y = CircularMenuWindow.class.getName() + ".position.y";
protected CircularMenuFloaty mFloaty;
protected WindowManager mWindowManager;
protected CircularActionMenu mCircularActionMenu;
@@ -66,6 +67,9 @@ public class CircularMenuWindow implements FloatyWindow {
}
private void setInitialState() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
int y = preferences.getInt(KEY_POSITION_Y, ScreenMetrics.getDeviceScreenHeight() / 2);
mActionViewWindowBridge.updatePosition(mActionViewWindowBridge.getX(), y);
keepToSide();
}
@@ -190,6 +194,11 @@ public class CircularMenuWindow implements FloatyWindow {
}
public void close() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
preferences.edit()
.putInt(KEY_POSITION_X, mActionViewWindowBridge.getX())
.putInt(KEY_POSITION_Y, mActionViewWindowBridge.getY())
.apply();
mOrientationEventListener.disable();
this.mWindowManager.removeView(this.mCircularActionMenu);
this.mWindowManager.removeView(this.mCircularActionView);

View File

@@ -37,6 +37,7 @@ import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.Pref;
import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.external.foreground.ForegroundService;
import org.autojs.autojs.model.explorer.Explorers;
import org.autojs.autojs.tool.AccessibilityServiceTool;
import org.autojs.autojs.ui.BaseActivity;
@@ -208,6 +209,7 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
public void exitCompletely() {
finish();
FloatyWindowManger.hideCircularMenu();
ForegroundService.stop(this);
stopService(new Intent(this, FloatyService.class));
AutoJs.getInstance().getScriptEngineService().stopAll();
}
@@ -264,6 +266,12 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
@Override
public void onBackPressed() {
Fragment fragment = mPagerAdapter.getStoredFragment(mViewPager.getCurrentItem());
if (fragment instanceof BackPressedHandler) {
if (((BackPressedHandler) fragment).onBackPressed(this)) {
return;
}
}
if (!mBackPressObserver.onBackPressed(this)) {
super.onBackPressed();
}

View File

@@ -3,14 +3,17 @@ package org.autojs.autojs.ui.main;
import android.support.annotation.CallSuper;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.View;
import com.stardust.util.BackPressedHandler;
/**
* Created by Stardust on 2017/8/22.
*/
public abstract class ViewPagerFragment extends Fragment {
public abstract class ViewPagerFragment extends Fragment implements BackPressedHandler {
protected static final int ROTATION_GONE = -1;

View File

@@ -72,23 +72,12 @@ public class CommunityFragment extends ViewPagerFragment implements BackPressedH
}
}
@Override
public void onResume() {
super.onResume();
((BackPressedHandler.HostActivity) getActivity())
.getBackPressedObserver()
.registerHandlerAtFront(this);
}
@Override
public void onPause() {
super.onPause();
Bundle savedWebViewState = new Bundle();
mWebView.saveState(savedWebViewState);
getArguments().putBundle("savedWebViewState", savedWebViewState);
((BackPressedHandler.HostActivity) getActivity())
.getBackPressedObserver()
.unregisterHandler(this);
}
@Override

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.ui.main.drawer;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -22,6 +23,7 @@ import com.stardust.notification.NotificationListenerService;
import org.autojs.autojs.Pref;
import org.autojs.autojs.R;
import org.autojs.autojs.external.foreground.ForegroundService;
import org.autojs.autojs.network.GlideApp;
import org.autojs.autojs.network.UserService;
import org.autojs.autojs.tool.EmptyObservers;
@@ -107,6 +109,8 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
};
private DrawerMenuItem mNotificationPermissionItem = new DrawerMenuItem(R.drawable.ic_ali_notification, R.string.text_notification_permission, 0, this::goToNotificationServiceSettings);
private DrawerMenuItem mForegroundServiceItem = new DrawerMenuItem(R.drawable.ic_service_green, R.string.text_foreground_service, R.string.key_foreground_servie, this::toggleForegroundService);
private DrawerMenuItem mFloatingWindowItem = new DrawerMenuItem(R.drawable.ic_robot_64, R.string.text_floating_window, 0, this::showOrDismissFloatingWindow);
private DrawerMenuItem mCheckForUpdatesItem = new DrawerMenuItem(R.drawable.ic_check_for_updates, R.string.text_check_for_updates, this::checkForUpdates);
@@ -142,6 +146,10 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
setChecked(mFloatingWindowItem, true);
}
setChecked(mConnectionItem, DevPluginService.getInstance().isConnected());
if(Pref.isForegroundServiceEnabled()){
ForegroundService.start(GlobalAppContext.get());
setChecked(mForegroundServiceItem, true);
}
}
private void initMenuItems() {
@@ -150,6 +158,7 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
mAccessibilityServiceItem,
mStableModeItem,
mNotificationPermissionItem,
mForegroundServiceItem,
new DrawerMenuGroup(R.string.text_script_record),
mFloatingWindowItem,
@@ -165,6 +174,7 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
}
@SuppressLint("CheckResult")
@Click(R.id.avatar)
void loginOrShowUserInfo() {
UserService.getInstance()
@@ -251,6 +261,17 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
}
}
private void toggleForegroundService(DrawerMenuItemViewHolder holder) {
boolean checked = holder.getSwitchCompat().isChecked();
if(checked){
ForegroundService.start(GlobalAppContext.get());
}else {
ForegroundService.stop(GlobalAppContext.get());
}
}
private void inputRemoteHost() {
String host = Pref.getServerAddressOrDefault(WifiTool.getRouterIp(getActivity()));
new MaterialDialog.Builder(getActivity())

View File

@@ -62,7 +62,6 @@ public class MarketFragment extends ViewPagerFragment implements BackPressedHand
@Override
public boolean onBackPressed(Activity activity) {
return false;
}

View File

@@ -5,7 +5,9 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.RecyclerView;
import com.stardust.app.GlobalAppContext;
import com.stardust.util.BackPressedHandler;
import com.stardust.util.IntentUtil;
@@ -26,6 +28,7 @@ import org.autojs.autojs.ui.main.ViewPagerFragment;
import org.autojs.autojs.ui.project.ProjectConfigActivity;
import org.autojs.autojs.ui.project.ProjectConfigActivity_;
import org.autojs.autojs.ui.viewmodel.ExplorerItemList;
import org.autojs.autojs.ui.widget.ExpandableRecyclerView;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
@@ -35,7 +38,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
* Created by Stardust on 2017/3/13.
*/
@EFragment(R.layout.fragment_my_script_list)
public class MyScriptListFragment extends ViewPagerFragment implements BackPressedHandler, FloatingActionMenu.OnFloatingActionButtonClickListener {
public class MyScriptListFragment extends ViewPagerFragment implements FloatingActionMenu.OnFloatingActionButtonClickListener {
private static final String TAG = "MyScriptListFragment";
@@ -63,27 +66,11 @@ public class MyScriptListFragment extends ViewPagerFragment implements BackPress
if (item.isEditable()) {
Scripts.edit(item.toScriptFile());
} else {
IntentUtil.viewFile(getContext(), item.getPath());
IntentUtil.viewFile(GlobalAppContext.get(), item.getPath());
}
});
}
@Override
public void onResume() {
super.onResume();
((BackPressedHandler.HostActivity) getActivity())
.getBackPressedObserver()
.registerHandlerAtFront(this);
}
@Override
public void onPause() {
super.onPause();
((BackPressedHandler.HostActivity) getActivity())
.getBackPressedObserver()
.unregisterHandler(this);
}
@Override
protected void onFabClick(FloatingActionButton fab) {
initFloatingActionMenuIfNeeded(fab);
@@ -98,7 +85,7 @@ public class MyScriptListFragment extends ViewPagerFragment implements BackPress
private void initFloatingActionMenuIfNeeded(final FloatingActionButton fab) {
if (mFloatingActionMenu != null)
return;
mFloatingActionMenu = ((FloatingActionMenu) getActivity().findViewById(R.id.floating_action_menu));
mFloatingActionMenu = getActivity().findViewById(R.id.floating_action_menu);
mFloatingActionMenu.getState()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SimpleObserver<Boolean>() {

View File

@@ -1,24 +1,33 @@
package org.autojs.autojs.ui.main.task;
import android.content.Intent;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.engine.ScriptEngine;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.AutoFileSource;
import com.stardust.autojs.script.JavaScriptSource;
import com.stardust.pio.PFiles;
import com.stardust.util.MapBuilder;
import org.autojs.autojs.R;
import org.autojs.autojs.timing.IntentTask;
import org.autojs.autojs.timing.TimedTask;
import org.autojs.autojs.timing.TimedTaskManager;
import org.joda.time.format.DateTimeFormat;
import java.util.Map;
import static org.autojs.autojs.ui.timing.TimedTaskSettingActivity.ACTION_DESC_MAP;
/**
* Created by Stardust on 2017/11/28.
*/
public abstract class Task {
public abstract String getName();
public abstract String getDesc();
@@ -29,11 +38,26 @@ public abstract class Task {
public static class PendingTask extends Task {
private TimedTask mTimedTask;
private IntentTask mIntentTask;
public PendingTask(TimedTask timedTask) {
mTimedTask = timedTask;
mIntentTask = null;
}
public PendingTask(IntentTask intentTask) {
mIntentTask = intentTask;
mTimedTask = null;
}
public boolean taskEquals(Object task) {
if (mTimedTask != null) {
return mTimedTask.equals(task);
}
return mIntentTask.equals(task);
}
public TimedTask getTimedTask() {
@@ -42,24 +66,47 @@ public abstract class Task {
@Override
public String getName() {
return PFiles.getSimplifiedPath(mTimedTask.getScriptPath());
return PFiles.getSimplifiedPath(getScriptPath());
}
@Override
public String getDesc() {
long nextTime = mTimedTask.getNextTime();
return GlobalAppContext.getString(R.string.text_next_run_time) + ": " +
DateTimeFormat.shortDateTime().print(nextTime);
if (mTimedTask != null) {
long nextTime = mTimedTask.getNextTime();
return GlobalAppContext.getString(R.string.text_next_run_time) + ": " +
DateTimeFormat.shortDateTime().print(nextTime);
} else {
assert mIntentTask != null;
Integer desc = ACTION_DESC_MAP.get(mIntentTask.getAction());
if(desc != null){
return GlobalAppContext.getString(desc);
}
return mIntentTask.getAction();
}
}
@Override
public void cancel() {
TimedTaskManager.getInstance().removeTask(mTimedTask);
if (mTimedTask != null) {
TimedTaskManager.getInstance().removeTask(mTimedTask);
} else {
TimedTaskManager.getInstance().removeTask(mIntentTask);
}
}
private String getScriptPath() {
if (mTimedTask != null) {
return mTimedTask.getScriptPath();
} else {
assert mIntentTask != null;
return mIntentTask.getScriptPath();
}
}
@Override
public String getEngineName() {
if (mTimedTask.getScriptPath().endsWith(".js")) {
if (getScriptPath().endsWith(".js")) {
return JavaScriptSource.ENGINE;
} else {
return AutoFileSource.ENGINE;
@@ -69,6 +116,16 @@ public abstract class Task {
public void setTimedTask(TimedTask timedTask) {
mTimedTask = timedTask;
}
public void setIntentTask(IntentTask intentTask) {
mIntentTask = intentTask;
}
public long getId() {
if(mTimedTask != null)
return mTimedTask.getId();
return mIntentTask.getId();
}
}
public static class RunningTask extends Task {

View File

@@ -8,6 +8,7 @@ import com.stardust.autojs.execution.ScriptExecution;
import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.timing.IntentTask;
import org.autojs.autojs.timing.TimedTask;
import org.autojs.autojs.timing.TimedTaskManager;
@@ -55,39 +56,56 @@ public abstract class TaskGroup implements Parent<Task> {
@Override
public void refresh() {
List<TimedTask> timedTasks = TimedTaskManager.getInstance().getAllTasksAsList();
mTasks.clear();
for (TimedTask timedTask : timedTasks) {
for (TimedTask timedTask : TimedTaskManager.getInstance().getAllTasksAsList()) {
mTasks.add(new Task.PendingTask(timedTask));
}
for (IntentTask intentTask : TimedTaskManager.getInstance().getAllIntentTasksAsList()) {
mTasks.add(new Task.PendingTask(intentTask));
}
}
public int addTask(TimedTask timedTask) {
public int addTask(Object task) {
int pos = mTasks.size();
mTasks.add(new Task.PendingTask(timedTask));
if (task instanceof TimedTask) {
mTasks.add(new Task.PendingTask((TimedTask) task));
} else if (task instanceof IntentTask) {
mTasks.add(new Task.PendingTask((IntentTask) task));
} else {
throw new IllegalArgumentException("task = " + task);
}
return pos;
}
public int removeTask(TimedTask data) {
public int removeTask(Object data) {
int i = indexOf(data);
if (i >= 0)
mTasks.remove(i);
return i;
}
private int indexOf(TimedTask data) {
private int indexOf(Object data) {
for (int i = 0; i < mTasks.size(); i++) {
if (((Task.PendingTask) mTasks.get(i)).getTimedTask().equals(data)) {
Task.PendingTask task = (Task.PendingTask) mTasks.get(i);
if (task.taskEquals(data)) {
return i;
}
}
return -1;
}
public int updateTask(TimedTask task) {
public int updateTask(Object task) {
int i = indexOf(task);
if (i >= 0)
((Task.PendingTask) mTasks.get(i)).setTimedTask(task);
if (i >= 0) {
if (task instanceof TimedTask) {
((Task.PendingTask) mTasks.get(i)).setTimedTask((TimedTask) task);
} else if (task instanceof IntentTask) {
((Task.PendingTask) mTasks.get(i)).setIntentTask((IntentTask) task);
} else {
throw new IllegalArgumentException("task = " + task);
}
}
return i;
}
}

View File

@@ -13,30 +13,28 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.stardust.autojs.workground.WrapContentLinearLayoutManager;
import com.bignerdranch.expandablerecyclerview.ChildViewHolder;
import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter;
import com.bignerdranch.expandablerecyclerview.ParentViewHolder;
import com.raizlabs.android.dbflow.structure.BaseModel;
import com.stardust.autojs.ScriptEngineService;
import com.stardust.autojs.engine.ScriptEngineManager;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.execution.ScriptExecutionListener;
import com.stardust.autojs.execution.SimpleScriptExecutionListener;
import com.stardust.autojs.engine.ScriptEngine;
import com.stardust.autojs.script.AutoFileSource;
import com.stardust.autojs.workground.WrapContentLinearLayoutManager;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.external.tasker.TaskerScriptEditActivity;
import org.autojs.autojs.storage.database.ModelChange;
import org.autojs.autojs.timing.IntentTask;
import org.autojs.autojs.timing.TaskReceiver;
import org.autojs.autojs.timing.TimedTask;
import org.autojs.autojs.timing.TimedTaskManager;
import org.autojs.autojs.ui.timing.TimedTaskSettingActivity;
import org.autojs.autojs.ui.timing.TimedTaskSettingActivity_;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import java.util.ArrayList;
import java.util.List;
@@ -60,7 +58,7 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
private TaskGroup.PendingTaskGroup mPendingTaskGroup;
private Adapter mAdapter;
private Disposable mTimedTaskChangeDisposable;
private final ScriptEngineService mScriptEngineService = AutoJs.getInstance().getScriptEngineService();
private Disposable mIntentTaskChangeDisposable;
private ScriptExecutionListener mScriptExecutionListener = new SimpleScriptExecutionListener() {
@Override
public void onStart(final ScriptExecution execution) {
@@ -135,7 +133,10 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
AutoJs.getInstance().getScriptEngineService().registerGlobalScriptExecutionListener(mScriptExecutionListener);
mTimedTaskChangeDisposable = TimedTaskManager.getInstance().getTimeTaskChanges()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onTimedTaskChange);
.subscribe(this::onTaskChange);
mIntentTaskChangeDisposable = TimedTaskManager.getInstance().getIntentTaskChanges()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onTaskChange);
}
@Override
@@ -151,26 +152,25 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
super.onDetachedFromWindow();
AutoJs.getInstance().getScriptEngineService().unregisterGlobalScriptExecutionListener(mScriptExecutionListener);
mTimedTaskChangeDisposable.dispose();
mIntentTaskChangeDisposable.dispose();
}
void onTimedTaskChange(ModelChange<TimedTask> taskChange) {
if (taskChange.getAction() == BaseModel.Action.INSERT) {
void onTaskChange(ModelChange taskChange) {
if (taskChange.getAction() == ModelChange.INSERT) {
mAdapter.notifyChildInserted(1, mPendingTaskGroup.addTask(taskChange.getData()));
} else if (taskChange.getAction() == BaseModel.Action.DELETE) {
} else if (taskChange.getAction() == ModelChange.DELETE) {
final int i = mPendingTaskGroup.removeTask(taskChange.getData());
// FIXME: 2017/11/28 task id is always 0
if (i >= 0) {
mAdapter.notifyChildRemoved(1, i);
} else {
Log.w(LOG_TAG, "data inconsistent on change: " + taskChange);
refresh();
}
} else if (taskChange.getAction() == BaseModel.Action.UPDATE) {
} else if (taskChange.getAction() == ModelChange.UPDATE) {
final int i = mPendingTaskGroup.updateTask(taskChange.getData());
if (i >= 0) {
mAdapter.notifyChildChanged(1, i);
} else {
Log.w(LOG_TAG, "data inconsistent on change: " + taskChange);
refresh();
}
}
@@ -249,8 +249,11 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
void onItemClick(View view) {
if (mTask instanceof Task.PendingTask) {
Task.PendingTask task = (Task.PendingTask) mTask;
String extra = task.getTimedTask() == null ? TimedTaskSettingActivity.EXTRA_INTENT_TASK_ID
: TimedTaskSettingActivity.EXTRA_TASK_ID;
TimedTaskSettingActivity_.intent(getContext())
.extra(TaskReceiver.EXTRA_TASK_ID, ((Task.PendingTask) mTask).getTimedTask().getId())
.extra(extra, task.getId())
.start();
}
}
@@ -263,8 +266,8 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
TaskGroupViewHolder(@NonNull View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
icon = (ImageView) itemView.findViewById(R.id.icon);
title = itemView.findViewById(R.id.title);
icon = itemView.findViewById(R.id.icon);
itemView.setOnClickListener(view -> {
if (isExpanded()) {
collapseView();

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.ui.main.task;
import android.app.Activity;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.widget.SwipeRefreshLayout;
@@ -70,4 +71,8 @@ public class TaskManagerFragment extends ViewPagerFragment {
AutoJs.getInstance().getScriptEngineService().stopAll();
}
@Override
public boolean onBackPressed(Activity activity) {
return false;
}
}

View File

@@ -39,6 +39,7 @@ import org.autojs.autojs.ui.shortcut.ShortcutIconSelectActivity_;
import java.io.File;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Locale;
import java.util.concurrent.Callable;
@@ -116,7 +117,6 @@ public class BuildActivity extends BaseActivity implements AutoJsApkBuilder.Prog
if (version < ApkBuilderPluginHelper.getSuitablePluginVersion()) {
showPluginDownloadDialog(R.string.apk_builder_plugin_version_too_low, false);
}
}
private void showPluginDownloadDialog(int msgRes, boolean finishIfCanceled) {

View File

@@ -12,6 +12,7 @@ import android.widget.ImageView;
import android.widget.Toast;
import com.stardust.autojs.project.ProjectConfig;
import com.stardust.autojs.runtime.api.Dialogs;
import com.stardust.pio.PFiles;
import org.androidannotations.annotations.AfterViews;
@@ -24,6 +25,7 @@ import org.autojs.autojs.model.explorer.ExplorerFileItem;
import org.autojs.autojs.model.explorer.ExplorerItem;
import org.autojs.autojs.model.explorer.Explorers;
import org.autojs.autojs.model.project.ProjectTemplate;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import org.autojs.autojs.ui.BaseActivity;
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
@@ -89,11 +91,21 @@ public class ProjectConfigActivity extends BaseActivity {
}
mDirectory = new File(dir);
mProjectConfig = ProjectConfig.fromProjectDir(dir);
if (mProjectConfig == null) {
new ThemeColorMaterialDialogBuilder(this)
.title(R.string.text_invalid_project)
.positiveText(R.string.ok)
.dismissListener(dialogInterface -> finish())
.show();
}
}
}
@AfterViews
void setupViews() {
if (mProjectConfig == null) {
return;
}
setToolbarAsBack(mNewProject ? getString(R.string.text_new_project) : mProjectConfig.getName());
if (mNewProject) {
mAppName.addTextChangedListener(new SimpleTextWatcher(s ->

View File

@@ -12,7 +12,7 @@ import android.view.View;
import com.stardust.theme.app.ColorSelectActivity;
import com.stardust.theme.preference.ThemeColorPreferenceFragment;
import com.stardust.theme.util.ListBuilder;
import com.stardust.util.MapEntries;
import com.stardust.util.MapBuilder;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
@@ -100,14 +100,14 @@ public class SettingsActivity extends BaseActivity {
@Override
public void onStart() {
super.onStart();
ACTION_MAP = new MapEntries<String, Runnable>()
.entry(getString(R.string.text_theme_color), () -> selectThemeColor(getActivity()))
.entry(getString(R.string.text_check_for_updates), () -> new UpdateCheckDialog(getActivity())
ACTION_MAP = new MapBuilder<String, Runnable>()
.put(getString(R.string.text_theme_color), () -> selectThemeColor(getActivity()))
.put(getString(R.string.text_check_for_updates), () -> new UpdateCheckDialog(getActivity())
.show())
.entry(getString(R.string.text_issue_report), () -> startActivity(new Intent(getActivity(), IssueReporterActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)))
.entry(getString(R.string.text_about_me_and_repo), () -> startActivity(new Intent(getActivity(), AboutActivity_.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)))
.entry(getString(R.string.text_licenses), () -> showLicenseDialog())
.map();
.put(getString(R.string.text_issue_report), () -> startActivity(new Intent(getActivity(), IssueReporterActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)))
.put(getString(R.string.text_about_me_and_repo), () -> startActivity(new Intent(getActivity(), AboutActivity_.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)))
.put(getString(R.string.text_licenses), () -> showLicenseDialog())
.build();
}
@Override

View File

@@ -51,7 +51,7 @@ public class SplashActivity extends BaseActivity {
mNotStartMainActivity = getIntent().getBooleanExtra(NOT_START_MAIN_ACTIVITY, false);
boolean forceShowAd = getIntent().getBooleanExtra(FORCE_SHOW_AD, false);
setContentView(R.layout.activity_splash);
mFullScreenAdView = (FullScreenAdView) findViewById(R.id.full_screen_view);
mFullScreenAdView = findViewById(R.id.full_screen_view);
if (!forceShowAd && !Pref.shouldShowAd()) {
mFullScreenAdView.setVisibility(View.INVISIBLE);
mHandler.postDelayed(this::enterNextActivity, 1500);

View File

@@ -3,6 +3,8 @@ package org.autojs.autojs.ui.timing;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.content.Intent;
import android.graphics.ColorFilter;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -18,6 +20,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
@@ -27,6 +30,9 @@ import android.widget.Toast;
import com.github.aakira.expandablelayout.ExpandableRelativeLayout;
import com.stardust.autojs.execution.ExecutionConfig;
import com.stardust.util.BiMap;
import com.stardust.util.BiMaps;
import com.stardust.util.MapBuilder;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.CheckedChange;
@@ -49,6 +55,7 @@ import org.joda.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by Stardust on 2017/11/28.
@@ -56,12 +63,51 @@ import java.util.List;
@EActivity(R.layout.activity_timed_task_setting)
public class TimedTaskSettingActivity extends BaseActivity {
public static final String EXTRA_INTENT_TASK_ID = "intent_task_id";
public static final String EXTRA_TASK_ID = TaskReceiver.EXTRA_TASK_ID;
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormat.forPattern("HH:mm");
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("yy-MM-dd");
private static final int REQUEST_CODE_IGNORE_BATTERY = 27101;
private static final String LOG_TAG = "TimedTaskSettings";
public static final Map<String, Integer> ACTION_DESC_MAP = new MapBuilder<String, Integer>()
.put(Intent.ACTION_BOOT_COMPLETED, R.string.text_run_on_boot)
.put(Intent.ACTION_SCREEN_OFF, R.string.text_run_on_screen_off)
.put(Intent.ACTION_SCREEN_ON, R.string.text_run_on_screen_on)
.put(Intent.ACTION_USER_PRESENT, R.string.text_run_on_screen_unlock)
.put(Intent.ACTION_BATTERY_CHANGED, R.string.text_run_on_battery_change)
.put(Intent.ACTION_POWER_CONNECTED, R.string.text_run_on_power_connect)
.put(Intent.ACTION_POWER_DISCONNECTED, R.string.text_run_on_power_disconnect)
.put(ConnectivityManager.CONNECTIVITY_ACTION, R.string.text_run_on_conn_change)
.put(Intent.ACTION_PACKAGE_ADDED, R.string.text_run_on_package_install)
.put(Intent.ACTION_PACKAGE_REMOVED, R.string.text_run_on_package_uninstall)
.put(Intent.ACTION_PACKAGE_REPLACED, R.string.text_run_on_package_update)
.put(Intent.ACTION_HEADSET_PLUG, R.string.text_run_on_headset_plug)
.put(Intent.ACTION_CONFIGURATION_CHANGED, R.string.text_run_on_config_change)
.put(Intent.ACTION_TIME_TICK, R.string.text_run_on_time_tick)
.build();
private static final BiMap<Integer, String> ACTIONS = BiMaps.<Integer, String>newBuilder()
.put(R.id.run_on_boot, Intent.ACTION_BOOT_COMPLETED)
.put(R.id.run_on_screen_off, Intent.ACTION_SCREEN_OFF)
.put(R.id.run_on_screen_on, Intent.ACTION_SCREEN_ON)
.put(R.id.run_on_screen_unlock, Intent.ACTION_USER_PRESENT)
.put(R.id.run_on_battery_change, Intent.ACTION_BATTERY_CHANGED)
.put(R.id.run_on_power_connect, Intent.ACTION_POWER_CONNECTED)
.put(R.id.run_on_power_disconnect, Intent.ACTION_POWER_DISCONNECTED)
.put(R.id.run_on_conn_change, ConnectivityManager.CONNECTIVITY_ACTION)
.put(R.id.run_on_package_install, Intent.ACTION_PACKAGE_ADDED)
.put(R.id.run_on_package_uninstall, Intent.ACTION_PACKAGE_REMOVED)
.put(R.id.run_on_package_update, Intent.ACTION_PACKAGE_REPLACED)
.put(R.id.run_on_headset_plug, Intent.ACTION_HEADSET_PLUG)
.put(R.id.run_on_config_change, Intent.ACTION_CONFIGURATION_CHANGED)
.put(R.id.run_on_time_tick, Intent.ACTION_TIME_TICK)
.build();
@ViewById(R.id.toolbar)
Toolbar mToolbar;
@@ -77,9 +123,17 @@ public class TimedTaskSettingActivity extends BaseActivity {
@ViewById(R.id.weekly_task_radio)
RadioButton mWeeklyTaskRadio;
@ViewById(R.id.run_on_boot_radio)
RadioButton mRunOnBootRadio;
@ViewById(R.id.run_on_broadcast)
RadioButton mRunOnBroadcastRadio;
@ViewById(R.id.run_on_other_broadcast)
RadioButton mRunOnOtherBroadcast;
@ViewById(R.id.action)
EditText mOtherBroadcastAction;
@ViewById(R.id.broadcast_group)
RadioGroup mBroadcastGroup;
@ViewById(R.id.disposable_task_time)
TextView mDisposableTaskTime;
@@ -98,25 +152,33 @@ public class TimedTaskSettingActivity extends BaseActivity {
private List<CheckBox> mDayOfWeekCheckBoxes = new ArrayList<>();
private ScriptFile mScriptFile;
private TimedTask mTimedTask;
private IntentTask mIntentTask;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int taskId = getIntent().getIntExtra(TaskReceiver.EXTRA_TASK_ID, -1);
long taskId = getIntent().getLongExtra(EXTRA_TASK_ID, -1);
if (taskId != -1) {
mTimedTask = TimedTaskManager.getInstance().getTimedTask(taskId);
if (mTimedTask != null) {
mScriptFile = new ScriptFile(mTimedTask.getScriptPath());
}
} else {
String path = getIntent().getStringExtra(ScriptIntents.EXTRA_KEY_PATH);
if (TextUtils.isEmpty(path)) {
finish();
long intentTaskId = getIntent().getLongExtra(EXTRA_INTENT_TASK_ID, -1);
if (intentTaskId != -1) {
mIntentTask = TimedTaskManager.getInstance().getIntentTask(intentTaskId);
if (mIntentTask != null) {
mScriptFile = new ScriptFile(mIntentTask.getScriptPath());
}
} else {
String path = getIntent().getStringExtra(ScriptIntents.EXTRA_KEY_PATH);
if (TextUtils.isEmpty(path)) {
finish();
}
mScriptFile = new ScriptFile(path);
}
mScriptFile = new ScriptFile(path);
}
}
@@ -128,7 +190,7 @@ public class TimedTaskSettingActivity extends BaseActivity {
mToolbar.setSubtitle(mScriptFile.getName());
}
findDayOfWeekCheckBoxes(mWeeklyTaskContainer);
setUpTime();
setUpTaskSettings();
}
private void findDayOfWeekCheckBoxes(ViewGroup parent) {
@@ -145,13 +207,32 @@ public class TimedTaskSettingActivity extends BaseActivity {
}
private void setUpTime() {
private void setUpTaskSettings() {
mDisposableTaskDate.setText(DATE_FORMATTER.print(LocalDate.now()));
mDisposableTaskTime.setText(TIME_FORMATTER.print(LocalTime.now()));
if (mTimedTask == null) {
mDailyTaskRadio.setChecked(true);
if (mTimedTask != null) {
setupTime();
return;
}
if (mIntentTask != null) {
setupAction();
return;
}
mDailyTaskRadio.setChecked(true);
}
private void setupAction() {
mRunOnBroadcastRadio.setChecked(true);
Integer buttonId = ACTIONS.getKey(mIntentTask.getAction());
if (buttonId == null) {
mRunOnOtherBroadcast.setChecked(true);
mOtherBroadcastAction.setText(mIntentTask.getAction());
} else {
((RadioButton) findViewById(buttonId)).setChecked(true);
}
}
private void setupTime() {
if (mTimedTask.isDisposable()) {
mDisposableTaskRadio.setChecked(true);
mDisposableTaskTime.setText(TIME_FORMATTER.print(mTimedTask.getMillis()));
@@ -171,11 +252,10 @@ public class TimedTaskSettingActivity extends BaseActivity {
mDayOfWeekCheckBoxes.get(i).setChecked(mTimedTask.hasDayOfWeek(i + 1));
}
}
}
@CheckedChange({R.id.daily_task_radio, R.id.weekly_task_radio, R.id.disposable_task_radio})
@CheckedChange({R.id.daily_task_radio, R.id.weekly_task_radio, R.id.disposable_task_radio, R.id.run_on_broadcast})
void onCheckedChanged(CompoundButton button) {
ExpandableRelativeLayout relativeLayout = findExpandableLayoutOf(button);
if (button.isChecked()) {
@@ -269,10 +349,8 @@ public class TimedTaskSettingActivity extends BaseActivity {
startActivityForResult(new Intent().setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
.setData(Uri.parse("package:" + getPackageName())), REQUEST_CODE_IGNORE_BATTERY);
} else {
createOrUpdateTimedTask();
createOrUpdateTask();
}
return true;
}
return super.onOptionsItemSelected(item);
@@ -282,18 +360,14 @@ public class TimedTaskSettingActivity extends BaseActivity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_IGNORE_BATTERY) {
Log.d(LOG_TAG, "result code = " + requestCode);
createOrUpdateTimedTask();
createOrUpdateTask();
}
super.onActivityResult(requestCode, resultCode, data);
}
private void createOrUpdateTimedTask() {
if (mRunOnBootRadio.isChecked()) {
IntentTask task = new IntentTask();
task.setAction(Intent.ACTION_BOOT_COMPLETED);
task.setScriptPath(mScriptFile.getPath());
TimedTaskManager.getInstance().addTask(task);
finish();
private void createOrUpdateTask() {
if (mRunOnBroadcastRadio.isChecked()) {
createOrUpdateIntentTask();
return;
}
TimedTask task = createTimedTask();
@@ -301,6 +375,9 @@ public class TimedTaskSettingActivity extends BaseActivity {
return;
if (mTimedTask == null) {
TimedTaskManager.getInstance().addTask(task);
if (mIntentTask != null) {
TimedTaskManager.getInstance().removeTask(mIntentTask);
}
Toast.makeText(this, R.string.text_already_create, Toast.LENGTH_SHORT).show();
} else {
task.setId(mTimedTask.getId());
@@ -308,4 +385,38 @@ public class TimedTaskSettingActivity extends BaseActivity {
}
finish();
}
private void createOrUpdateIntentTask() {
int buttonId = mBroadcastGroup.getCheckedRadioButtonId();
if (buttonId == -1) {
Toast.makeText(this, R.string.error_empty_selection, Toast.LENGTH_SHORT).show();
return;
}
String action;
if (buttonId == R.id.run_on_other_broadcast) {
action = mOtherBroadcastAction.getText().toString();
if (action.isEmpty()) {
mOtherBroadcastAction.setError(getString(R.string.text_should_not_be_empty));
return;
}
} else {
action = ACTIONS.get(buttonId);
}
IntentTask task = new IntentTask();
task.setAction(action);
task.setScriptPath(mScriptFile.getPath());
if (mIntentTask != null) {
task.setId(mIntentTask.getId());
TimedTaskManager.getInstance().updateTask(task);
Toast.makeText(this, R.string.text_already_create, Toast.LENGTH_SHORT).show();
} else {
TimedTaskManager.getInstance().addTask(task);
if (mTimedTask != null) {
TimedTaskManager.getInstance().removeTask(mTimedTask);
}
}
finish();
}
}

View File

@@ -58,7 +58,7 @@ public class BubblePopupMenu extends PopupWindow {
} else {
params.leftMargin = 0;
}
if (y + height > screenHeight) {
if (y > screenHeight / 2) {
getContentView().setRotation(180);
mRecyclerView.setRotation(180);
params.leftMargin = -params.leftMargin;

View File

@@ -253,12 +253,175 @@
</com.github.aakira.expandablelayout.ExpandableRelativeLayout>
<RadioButton
android:id="@+id/run_on_boot_radio"
android:id="@+id/run_on_broadcast"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="16dp"
android:text="@string/text_run_on_boot"/>
android:text="@string/text_run_on_broadcast"/>
<com.github.aakira.expandablelayout.ExpandableRelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:ael_expanded="false"
app:ael_interpolator="fastOutSlowIn"
app:ael_orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:orientation="vertical">
<RadioGroup
android:id="@+id/broadcast_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/run_on_boot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_boot"/>
<RadioButton
android:id="@+id/run_on_screen_on"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_screen_on"/>
<RadioButton
android:id="@+id/run_on_screen_off"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_screen_off"/>
<RadioButton
android:id="@+id/run_on_screen_unlock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_screen_unlock"/>
<RadioButton
android:id="@+id/run_on_battery_change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_battery_change"/>
<RadioButton
android:id="@+id/run_on_power_connect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_power_connect"/>
<RadioButton
android:id="@+id/run_on_power_disconnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_power_disconnect"/>
<RadioButton
android:id="@+id/run_on_conn_change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_conn_change"/>
<RadioButton
android:id="@+id/run_on_package_install"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_package_install"/>
<RadioButton
android:id="@+id/run_on_package_uninstall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_package_uninstall"/>
<RadioButton
android:id="@+id/run_on_package_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_package_update"/>
<RadioButton
android:id="@+id/run_on_headset_plug"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_headset_plug"/>
<RadioButton
android:id="@+id/run_on_config_change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_config_change"/>
<RadioButton
android:id="@+id/run_on_time_tick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_time_tick"/>
<RadioButton
android:id="@+id/run_on_other_broadcast"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingLeft="8dp"
android:text="@string/text_run_on_other_broadcast"/>
</RadioGroup>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="36dp"
android:layout_marginTop="2dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_broadcast_action"
android:maxLines="2"
android:text="@string/text_broadcast_action_prefix"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout>
</com.github.aakira.expandablelayout.ExpandableRelativeLayout>
</RadioGroup>
</LinearLayout>

View File

@@ -382,7 +382,7 @@
<string name="text_use_alarm_clock">使用系统闹钟唤醒Auto.js</string>
<string name="error_connect_to_remote">连接失败: %s</string>
<string name="text_are_you_sure_to_delete">确定要删除%s吗</string>
<string name="text_run_on_boot">开机时运行</string>
<string name="text_run_on_broadcast">特定事件(广播)触发运行</string>
<string name="text_search_java_class">搜索Java包/类</string>
<string name="text_class_or_package_name">类/包名</string>
<string name="text_view_docs">查看文档</string>
@@ -397,4 +397,28 @@
<string name="text_project_location">项目位置</string>
<string name="text_new_project">新建项目</string>
<string name="text_js_file">js文件</string>
<string name="text_invalid_project">无效项目</string>
<string name="text_foreground_service">前台服务</string>
<string name="key_foreground_servie">key_foreground_service</string>
<string name="foreground_notification_channel_name">前台服务通知</string>
<string name="foreground_notification_title">Auto.js保持运行中</string>
<string name="foreground_notification_text">点击进入主界面</string>
<string name="text_run_on_other_broadcast">其他事件(广播)</string>
<string name="text_broadcast_action">广播Action</string>
<string name="text_broadcast_action_prefix">android.intent.action.</string>
<string name="text_run_on_boot">开机时</string>
<string name="text_run_on_screen_on">亮屏时</string>
<string name="text_run_on_screen_off">息屏时</string>
<string name="error_empty_selection">请选择一个广播事件选项</string>
<string name="text_run_on_battery_change">电量变化时</string>
<string name="text_run_on_screen_unlock">屏幕解锁时</string>
<string name="text_run_on_power_connect">电源连接时</string>
<string name="text_run_on_power_disconnect">电源断开时</string>
<string name="text_run_on_conn_change">网络连接变化时</string>
<string name="text_run_on_package_install">新应用安装时</string>
<string name="text_run_on_package_uninstall">应用卸载时</string>
<string name="text_run_on_package_update">应用更新时</string>
<string name="text_run_on_headset_plug">耳机插拔时</string>
<string name="text_run_on_time_tick">每分钟一次</string>
<string name="text_run_on_config_change">某些设置(屏幕方向,地区等)更改时</string>
</resources>

View File

@@ -19,7 +19,7 @@ module.exports = function (runtime, global) {
xml = xml.toXMLString();
}
runtime.ui.layoutInflater.setContext(activity);
var view = runtime.ui.layoutInflater.inflate(xml);
var view = runtime.ui.layoutInflater.inflate(xml, activity.window.decorView, false);
ui.setContentView(view);
}
@@ -80,7 +80,7 @@ module.exports = function (runtime, global) {
ui.post = function (action, delay) {
delay = delay || 0;
runtime.getUiHandler().postDelay(wrapUiAction(action), delay);
runtime.getUiHandler().postDelayed(wrapUiAction(action), delay);
}
ui.statusBarColor = function (color) {

View File

@@ -65,6 +65,7 @@ public abstract class AutoJs {
mNotificationObserver = new AccessibilityNotificationObserver(mContext);
mAccessibilityInfoProvider = new AccessibilityInfoProvider(mContext.getPackageManager());
mScriptEngineService = buildScriptEngineService();
ScriptEngineService.setInstance(mScriptEngineService);
init();
}

View File

@@ -67,9 +67,7 @@ public class ScriptEngineService {
}
private void onFinish(ScriptExecution execution) {
if (execution.getEngine() instanceof JavaScriptEngine) {
((JavaScriptEngine) execution.getEngine()).getRuntime().onExit();
}
}
@Override
@@ -87,6 +85,7 @@ public class ScriptEngineService {
};
private static ScriptEngineService sInstance;
private final Context mContext;
private UiHandler mUiHandler;
private final Console mGlobalConsole;
@@ -211,6 +210,18 @@ public class ScriptEngineService {
return mScriptExecutions.get(id);
}
public static void setInstance(ScriptEngineService service) {
if (sInstance != null) {
throw new IllegalStateException();
}
sInstance = service;
}
public static ScriptEngineService getInstance() {
return sInstance;
}
private static class EngineLifecycleObserver implements ScriptEngineManager.EngineLifecycleCallback {
private final Set<ScriptEngineManager.EngineLifecycleCallback> mEngineLifecycleCallbacks = new LinkedHashSet<>();

View File

@@ -17,12 +17,12 @@ public class ReadOnlyUiObject extends UiObject {
private NodeInfo mNodeInfo;
public ReadOnlyUiObject(NodeInfo info) {
super(info, info.depth, -1);
super(null, info.depth, -1);
mNodeInfo = info;
}
public ReadOnlyUiObject(NodeInfo info, int indexInParent) {
super(info, info.depth, indexInParent);
super(null, info.depth, indexInParent);
mNodeInfo = info;
}

View File

@@ -5,7 +5,6 @@ import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
import com.android.dx.util.IntSet;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.runtime.api.Threads;
import com.stardust.autojs.runtime.api.Timers;
@@ -33,7 +32,7 @@ public class Loopers implements MessageQueue.IdleHandler {
private volatile ThreadLocal<Boolean> waitWhenIdle = new ThreadLocal<>();
private volatile ThreadLocal<HashSet<Integer>> waitIds = new ThreadLocal<>();
private volatile ThreadLocal<Integer> maxWaitId = new ThreadLocal<>();
private volatile ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHanders = new ThreadLocal<>();
private volatile ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHandlers = new ThreadLocal<>();
private volatile Looper mServantLooper;
private Timers mTimers;
private ScriptRuntime mScriptRuntime;
@@ -58,17 +57,17 @@ public class Loopers implements MessageQueue.IdleHandler {
return mMainLooper;
}
public void addLooperQuiteHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
public void addLooperQuitHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
if (handlers == null) {
handlers = new CopyOnWriteArrayList<>();
looperQuitHanders.set(handlers);
looperQuitHandlers.set(handlers);
}
handlers.add(handler);
}
public boolean removeLooperQuiteHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
public boolean removeLooperQuitHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
return handlers != null && handlers.remove(handler);
}
@@ -82,7 +81,7 @@ public class Loopers implements MessageQueue.IdleHandler {
if (waitWhenIdle.get() || !waitIds.get().isEmpty()) {
return false;
}
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
if (handlers == null) {
return true;
}

View File

@@ -136,7 +136,7 @@ public class BlockedMaterialDialog extends MaterialDialog {
}
public MaterialDialog.Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices) {
dismissListener(dialog -> setAndNotify(new Integer[0]));
dismissListener(dialog -> setAndNotify(new int[0]));
super.itemsCallbackMultiChoice(selectedIndices, (dialog, which, text) -> {
setAndNotify(ArrayUtils.unbox(which));
return true;

View File

@@ -146,20 +146,24 @@ public class DynamicLayoutInflater {
}
public View inflate(String xml, @Nullable ViewGroup parent) {
return inflate(xml, parent, parent != null);
}
public View inflate(String xml, @Nullable ViewGroup parent, boolean attachToParent) {
View view = mLayoutInflaterDelegate.beforeInflation(xml, parent);
if (view != null)
return view;
xml = convertXml(xml);
return mLayoutInflaterDelegate.afterInflation(doInflation(xml, parent), xml, parent);
return mLayoutInflaterDelegate.afterInflation(doInflation(xml, parent, attachToParent), xml, parent);
}
protected View doInflation(String xml, @Nullable ViewGroup parent) {
protected View doInflation(String xml, @Nullable ViewGroup parent, boolean attachToParent) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(new ByteArrayInputStream(xml.getBytes()));
return inflate(document.getDocumentElement(), parent, true);
return inflate(document.getDocumentElement(), parent, attachToParent);
} catch (Exception e) {
throw new InflateException(e);
}

View File

@@ -51,7 +51,7 @@ public class JsImageView extends RoundedImageView {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
if (mCircle) {
setCornerRadius(getMeasuredWidth() / 2);
}

View File

@@ -45,6 +45,12 @@ public abstract class JavaScriptEngine extends ScriptEngine.AbstractScriptEngine
return (ScriptSource) getTag(TAG_SOURCE);
}
@Override
public synchronized void destroy() {
mRuntime.onExit();
super.destroy();
}
@Override
public String toString() {
return "ScriptEngine@" + Integer.toHexString(hashCode()) + "{" +

View File

@@ -5,6 +5,7 @@ import android.support.annotation.CallSuper;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.ScriptSource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -68,7 +69,7 @@ public interface ScriptEngine<S extends ScriptSource> {
abstract class AbstractScriptEngine<S extends ScriptSource> implements ScriptEngine<S> {
private Map<String, Object> mTags = new ConcurrentHashMap<>();
private Map<String, Object> mTags = new HashMap<>();
private OnDestroyListener mOnDestroyListener;
private boolean mDestroyed = false;
private Exception mUncaughtException;
@@ -76,9 +77,11 @@ public interface ScriptEngine<S extends ScriptSource> {
@Override
public synchronized void setTag(String key, Object value) {
if (value == null)
return;
mTags.put(key, value);
if (value == null) {
mTags.remove(key);
} else {
mTags.put(key, value);
}
}
@Override

View File

@@ -6,6 +6,7 @@ import com.stardust.autojs.engine.ScriptEngine;
import com.stardust.autojs.engine.ScriptEngineManager;
import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
import com.stardust.autojs.script.ScriptSource;
import com.stardust.lang.ThreadCompat;
/**
* Created by Stardust on 2017/5/1.
@@ -24,6 +25,7 @@ public class RunnableScriptExecution extends ScriptExecution.AbstractScriptExecu
@Override
public void run() {
ThreadCompat.currentThread().setName("ScriptThread-" + getId() + "[" + getSource() + "]");
execute();
}

View File

@@ -7,11 +7,13 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import com.stardust.autojs.ScriptEngineService;
import com.stardust.autojs.core.eventloop.EventEmitter;
import com.stardust.autojs.core.eventloop.SimpleEvent;
import com.stardust.autojs.engine.JavaScriptEngine;
@@ -21,7 +23,6 @@ import com.stardust.autojs.engine.ScriptEngineManager;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.runtime.api.UI;
import com.stardust.autojs.script.ScriptSource;
import com.stardust.util.IntentExtras;
import org.mozilla.javascript.NativeObject;
@@ -32,13 +33,13 @@ import org.mozilla.javascript.NativeObject;
public class ScriptExecuteActivity extends AppCompatActivity {
private static final String EXTRA_EXECUTION = ScriptExecuteActivity.class.getName() + ".execution";
private static final String LOG_TAG = "ScriptExecuteActivity";
private static final String EXTRA_EXECUTION_ID = ScriptExecuteActivity.class.getName() + ".execution_id";
private Object mResult;
private ScriptEngine mScriptEngine;
private ScriptExecutionListener mExecutionListener;
private ScriptSource mScriptSource;
private ActivityScriptExecution mScriptExecution;
private IntentExtras mIntentExtras;
private ScriptRuntime mRuntime;
@@ -47,10 +48,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
public static ActivityScriptExecution execute(Context context, ScriptEngineManager manager, ScriptExecutionTask task) {
ActivityScriptExecution execution = new ActivityScriptExecution(manager, task);
Intent i = new Intent(context, ScriptExecuteActivity.class)
.putExtra(EXTRA_EXECUTION_ID, execution.getId())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
IntentExtras.newExtras()
.put(EXTRA_EXECUTION, execution)
.putInIntent(i);
context.startActivity(i);
return execution;
}
@@ -59,36 +58,30 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIntentExtras = readIntentExtras(savedInstanceState);
if (mIntentExtras == null || mIntentExtras.get(EXTRA_EXECUTION) == null) {
finish();
int executionId = getIntent().getIntExtra(EXTRA_EXECUTION_ID, ScriptExecution.NO_ID);
if (executionId == ScriptExecution.NO_ID) {
super.finish();
return;
}
mScriptExecution = mIntentExtras.get(EXTRA_EXECUTION);
ScriptExecution execution = ScriptEngineService.getInstance().getScriptExecution(executionId);
if (execution == null || !(execution instanceof ActivityScriptExecution)) {
super.finish();
return;
}
mScriptExecution = (ActivityScriptExecution) execution;
mScriptSource = mScriptExecution.getSource();
mScriptEngine = mScriptExecution.createEngine(this);
mExecutionListener = mScriptExecution.getListener();
mRuntime = ((JavaScriptEngine) mScriptEngine).getRuntime();
mEventEmitter = new EventEmitter(mRuntime.bridges);
runScript();
emit("create", savedInstanceState);
}
public EventEmitter getEventEmitter() {
return mEventEmitter;
}
private IntentExtras readIntentExtras(Bundle savedInstanceState) {
IntentExtras extras = IntentExtras.fromIntentAndRelease(getIntent());
if (extras == null && savedInstanceState != null) {
int id = savedInstanceState.getInt(IntentExtras.EXTRA_ID, -1);
if (id == -1) {
return null;
}
extras = IntentExtras.fromIdAndRelease(id);
}
return extras;
}
private void runScript() {
try {
prepare();
@@ -130,6 +123,10 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
public void finish() {
if (mScriptExecution == null || mExecutionListener == null) {
super.finish();
return;
}
Exception exception = mScriptEngine.getUncaughtException();
if (exception != null) {
onException(exception);
@@ -142,6 +139,7 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "onDestroy");
mScriptEngine.put("activity", null);
mScriptEngine.setTag("activity", null);
mScriptEngine.destroy();
@@ -151,10 +149,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mIntentExtras == null)
return;
IntentExtras extras = IntentExtras.newExtras().putAll(mIntentExtras);
outState.putInt(IntentExtras.EXTRA_ID, extras.getId());
if (mScriptExecution != null)
outState.putInt(EXTRA_EXECUTION_ID, mScriptExecution.getId());
emit("save_instance_state", outState);
}

View File

@@ -88,7 +88,6 @@ public class ProjectConfig {
try {
return fromJson(PFiles.read(context.getAssets().open(path)));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@@ -97,7 +96,6 @@ public class ProjectConfig {
try {
return fromJson(PFiles.read(path));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

View File

@@ -3,6 +3,7 @@ package com.stardust.autojs.rhino;
import android.util.Log;
import com.android.dx.command.dexer.Main;
import com.android.dx.dex.file.DexFile;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;

View File

@@ -5,6 +5,7 @@ import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.R;
@@ -333,7 +334,7 @@ public class ScriptRuntime {
}
}
public void loadDex(String path){
public void loadDex(String path) {
path = files.path(path);
try {
((AndroidClassLoader) ContextFactory.getGlobal().getApplicationClassLoader()).loadDex(new File(path));
@@ -375,6 +376,7 @@ public class ScriptRuntime {
}
public void onExit() {
Log.d(TAG, "on exit");
//清除interrupt状态
ThreadCompat.interrupted();
//悬浮窗需要第一时间关闭以免出现恶意脚本全屏悬浮窗屏蔽屏幕并且在exit中写死循环的问题
@@ -398,6 +400,7 @@ public class ScriptRuntime {
}
ignoresException(sensors::unregisterAll);
ignoresException(timers::recycle);
ignoresException(ui::recycle);
}
private void ignoresException(Runnable r) {

View File

@@ -11,13 +11,12 @@ import com.stardust.autojs.core.eventloop.EventEmitter;
import com.stardust.autojs.core.looper.Loopers;
import com.stardust.autojs.runtime.ScriptBridges;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.util.MapEntries;
import com.stardust.util.MapBuilder;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by Stardust on 2018/2/5.
@@ -60,21 +59,21 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
}
private static final Map<String, Integer> SENSORS = new MapEntries<String, Integer>()
.entry("ACCELEROMETER", Sensor.TYPE_ACCELEROMETER)
.entry("MAGNETIC_FIELD", Sensor.TYPE_MAGNETIC_FIELD)
.entry("ORIENTATION", Sensor.TYPE_ORIENTATION)
.entry("GYROSCOPE", Sensor.TYPE_GYROSCOPE)
.entry("LIGHT", Sensor.TYPE_LIGHT)
.entry("TEMPERATURE", Sensor.TYPE_TEMPERATURE)
.entry("PRESSURE", Sensor.TYPE_PRESSURE)
.entry("AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.entry("PROXIMITY", Sensor.TYPE_PROXIMITY)
.entry("GRAVITY", Sensor.TYPE_GRAVITY)
.entry("LINEAR_ACCELERATION", Sensor.TYPE_LINEAR_ACCELERATION)
.entry("RELATIVE_HUMIDITY", Sensor.TYPE_RELATIVE_HUMIDITY)
.entry("AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.map();
private static final Map<String, Integer> SENSORS = new MapBuilder<String, Integer>()
.put("ACCELEROMETER", Sensor.TYPE_ACCELEROMETER)
.put("MAGNETIC_FIELD", Sensor.TYPE_MAGNETIC_FIELD)
.put("ORIENTATION", Sensor.TYPE_ORIENTATION)
.put("GYROSCOPE", Sensor.TYPE_GYROSCOPE)
.put("LIGHT", Sensor.TYPE_LIGHT)
.put("TEMPERATURE", Sensor.TYPE_TEMPERATURE)
.put("PRESSURE", Sensor.TYPE_PRESSURE)
.put("AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.put("PROXIMITY", Sensor.TYPE_PROXIMITY)
.put("GRAVITY", Sensor.TYPE_GRAVITY)
.put("LINEAR_ACCELERATION", Sensor.TYPE_LINEAR_ACCELERATION)
.put("RELATIVE_HUMIDITY", Sensor.TYPE_RELATIVE_HUMIDITY)
.put("AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE)
.build();
public boolean ignoresUnsupportedSensor = false;
public final Delay delay = new Delay();
@@ -92,7 +91,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
mScriptBridges = runtime.bridges;
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
mScriptRuntime = runtime;
runtime.loopers.addLooperQuiteHandler(this);
runtime.loopers.addLooperQuitHandler(this);
}
public SensorEventEmitter register(String sensorName) {
@@ -167,5 +166,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
}
mSensorEventEmitters.clear();
}
mScriptRuntime.loopers.removeLooperQuitHandler(this);
}
}

View File

@@ -46,7 +46,7 @@ public class Threads {
TimerThread thread = createThread(runnable);
synchronized (mThreads) {
mThreads.add(thread);
thread.setName(thread.getName() + " (Spawn-" + mSpawnCount + ")");
thread.setName(mMainThread.getName() + " (Spawn-" + mSpawnCount + ")");
mSpawnCount++;
}
thread.start();

View File

@@ -113,6 +113,10 @@ public class UI extends ProxyObject {
}
}
public void recycle(){
mDynamicLayoutInflater.setContext(null);
}
private class Drawables extends com.stardust.autojs.core.ui.inflater.util.Drawables {
@Override

View File

@@ -3,7 +3,7 @@ package com.stardust.autojs.script;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.stardust.util.MapEntries;
import com.stardust.util.MapBuilder;
import java.io.Reader;
import java.io.StringReader;
@@ -23,10 +23,10 @@ public abstract class JavaScriptSource extends ScriptSource {
public static final int EXECUTION_MODE_UI = 0x00000001;
public static final int EXECUTION_MODE_AUTO = 0x00000002;
private static final Map<String, Integer> EXECUTION_MODES = new MapEntries<String, Integer>()
.entry("ui", EXECUTION_MODE_UI)
.entry("auto", EXECUTION_MODE_AUTO)
.map();
private static final Map<String, Integer> EXECUTION_MODES = new MapBuilder<String, Integer>()
.put("ui", EXECUTION_MODE_UI)
.put("auto", EXECUTION_MODE_AUTO)
.build();
private static final int EXECUTION_MODE_STRING_MAX_LENGTH = 7;
private int mExecutionMode = -1;

View File

@@ -19,6 +19,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
@@ -35,7 +36,7 @@ public class JsBeautifier {
void onException(Exception e);
}
private Executor mExecutor = Executors.newSingleThreadExecutor();
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private Context mContext;
private Function mJsBeautifyFunction;
private org.mozilla.javascript.Context mScriptContext;
@@ -122,4 +123,9 @@ public class JsBeautifier {
}
}
public void shutdown(){
mExecutor.shutdownNow();
mView = null;
}
}

View File

@@ -1,17 +1,6 @@
package com.stardust.autojs.script;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import com.stardust.util.MapEntries;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Created by Stardust on 2017/4/2.

View File

@@ -0,0 +1,118 @@
package org.mozilla.javascript;
import android.os.Looper;
import android.util.Log;
import org.mozilla.javascript.jdk15.VMBridge_jdk15;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class VMBridge_custom extends VMBridge_jdk15 {
private static final String LOG_TAG = "VMBridge_custom";
public VMBridge_custom() throws SecurityException, InstantiationException {
}
@Override
protected Object newInterfaceProxy(Object proxyHelper, ContextFactory cf, InterfaceAdapter adapter, Object target, Scriptable topScope) {
// --- The following code is copied from super class --
Constructor<?> c = (Constructor) proxyHelper;
InvocationHandler handler = (proxy, method, args) -> {
if (method.getDeclaringClass() == Object.class) {
String methodName = method.getName();
if (methodName.equals("equals")) {
Object other = args[0];
return proxy == other;
}
if (methodName.equals("hashCode")) {
return target.hashCode();
}
if (methodName.equals("toString")) {
return "Proxy[" + target.toString() + "]";
}
}
// Add thread check
//Check if the current thread is ui thread
if (Looper.myLooper() == Looper.getMainLooper()) {
// If so, catch any exception of invoking
// Because an exception on ui thread will cause the whole app to crash
try {
Object result = adapter.invoke(cf, target, topScope, proxy, method, args);
return castReturnValue(method, result);
} catch (Exception e) {
e.printStackTrace();
// notify the script thread to exit
Object jsRuntime = topScope.get("runtime", null);
Log.d(LOG_TAG, "jsRuntime = " + jsRuntime);
if (jsRuntime instanceof NativeJavaObject) {
Object runtime = ((NativeJavaObject) jsRuntime).unwrap();
Log.d(LOG_TAG, "runtime = " + runtime);
if(runtime instanceof com.stardust.autojs.runtime.ScriptRuntime){
((com.stardust.autojs.runtime.ScriptRuntime) runtime).exit(e);
}
}
// even if we caught the exception, we must return a value to for the method call.
return defaultValue(method.getReturnType());
}
} else {
return castReturnValue(method, adapter.invoke(cf, target, topScope, proxy, method, args));
}
};
// --- The following code is copied from super class --
try {
Object proxy = c.newInstance(handler);
return proxy;
} catch (InvocationTargetException var10) {
throw Context.throwAsScriptRuntimeEx(var10);
} catch (IllegalAccessException var11) {
throw Kit.initCause(new IllegalStateException(), var11);
} catch (InstantiationException var12) {
throw Kit.initCause(new IllegalStateException(), var12);
}
}
// cast the return value to boolean if needed.
// if a javascript function that implements a java interface returns nothing,
// it will be regarded as "false", like javascript behavior, instead of reporting error "undefined cannot be cast to boolean"
protected Object castReturnValue(Method method, Object returnValue) {
if (method.getReturnType().equals(Boolean.TYPE) || method.getReturnType().equals(Boolean.class)) {
return ScriptRuntime.toBoolean(returnValue);
}
return returnValue;
}
protected Object defaultValue(Class<?> type) {
if (type.equals(Boolean.TYPE) || type.equals(Boolean.class)) {
return false;
}
if (type.equals(Integer.TYPE) || type.equals(Integer.class)) {
return 0;
}
if (type.equals(Long.TYPE) || type.equals(Long.class)) {
return 0L;
}
if (type.equals(Float.TYPE) || type.equals(Float.class)) {
return 0F;
}
if (type.equals(Double.TYPE) || type.equals(Double.class)) {
return 0.0;
}
if (type.equals(Byte.TYPE) || type.equals(Byte.class)) {
return (byte) 0;
}
if (type.equals(Character.TYPE) || type.equals(Character.class)) {
return (char) 0;
}
if (type.isAssignableFrom(CharSequence.class)) {
return "";
}
return null;
}
}

View File

@@ -7,7 +7,7 @@ import android.support.annotation.RequiresApi;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.automator.UiObject;
import com.stardust.util.MapEntries;
import com.stardust.util.MapBuilder;
import java.util.Map;
@@ -17,15 +17,15 @@ import java.util.Map;
public class ActionFactory {
private static Map<Integer, Object> searchUpAction = new MapEntries<Integer, Object>()
.entry(AccessibilityNodeInfo.ACTION_CLICK, null)
.entry(AccessibilityNodeInfo.ACTION_LONG_CLICK, null)
.entry(AccessibilityNodeInfo.ACTION_SELECT, null)
.entry(AccessibilityNodeInfo.ACTION_FOCUS, null)
.entry(AccessibilityNodeInfo.ACTION_SELECT, null)
.entry(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
.entry(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
.map();
private static Map<Integer, Object> searchUpAction = new MapBuilder<Integer, Object>()
.put(AccessibilityNodeInfo.ACTION_CLICK, null)
.put(AccessibilityNodeInfo.ACTION_LONG_CLICK, null)
.put(AccessibilityNodeInfo.ACTION_SELECT, null)
.put(AccessibilityNodeInfo.ACTION_FOCUS, null)
.put(AccessibilityNodeInfo.ACTION_SELECT, null)
.put(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
.put(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
.build();
public static SimpleAction createActionWithTextFilter(int action, String text, int index) {
if (searchUpAction.containsKey(action))

View File

@@ -24,7 +24,6 @@ import java.util.concurrent.locks.ReentrantLock;
public class AccessibilityService extends android.accessibilityservice.AccessibilityService {
private static final String TAG = "AccessibilityService";
private static final SortedMap<Integer, AccessibilityDelegate> mDelegates = new TreeMap<>();
@@ -55,13 +54,13 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
@Override
public void onAccessibilityEvent(final AccessibilityEvent event) {
instance = this;
Log.v(TAG, "onAccessibilityEvent: " + event);
//Log.v(TAG, "onAccessibilityEvent: " + event);
if (!containsAllEventTypes && !eventTypes.contains(event.getEventType()))
return;
int type = event.getEventType();
if (type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED ||
type == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
AccessibilityNodeInfo root = super.getRootInActiveWindow();
AccessibilityNodeInfo root = getRootInActiveWindow();
if (root != null) {
mFastRootInActiveWindow = root;
}
@@ -109,7 +108,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
public AccessibilityNodeInfo getRootInActiveWindow() {
try {
return super.getRootInActiveWindow();
} catch (IllegalStateException e) {
} catch (Exception e) {
return null;
}
}

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":416},"path":"commonRelease-4.0.2 Alpha11.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":421},"path":"commonRelease-4.0.3 Alpha2.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]

View File

@@ -0,0 +1,11 @@
package com.stardust.util;
import java.util.Map;
import java.util.Set;
public interface BiMap<K, V> extends Map<K, V> {
K getKey(V value);
Set<V> valueSet();
}

View File

@@ -0,0 +1,206 @@
package com.stardust.util;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
public class BiMaps {
public static <K, V> BiMap<K, V> make(Map<K, V> keyToValue, Map<V, K> valueToKey) {
return new BiMapImpl<>(keyToValue, valueToKey);
}
public static <K, V> BiMapBuilder<K, V> newBuilder() {
return new BiMapBuilder<>();
}
public static class BiMapBuilder<K, V> {
private final BiMap<K, V> mBiMap = make(new HashMap<K, V>(), new HashMap<V, K>());
public BiMapBuilder<K, V> put(K key, V value) {
mBiMap.put(key, value);
return this;
}
public BiMap<K, V> build() {
return mBiMap;
}
}
private static class BiMapImpl<K, V> implements BiMap<K, V> {
private final Map<K, V> mKVMap;
private final Map<V, K> mVKMap;
private BiMapImpl(Map<K, V> kvMap, Map<V, K> vkMap) {
mKVMap = kvMap;
mVKMap = vkMap;
}
@Override
public int size() {
return mKVMap.size();
}
@Override
public boolean isEmpty() {
return mKVMap.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return mKVMap.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return mVKMap.containsKey(value);
}
@Override
public V get(Object key) {
return mKVMap.get(key);
}
@Override
public V put(K key, V value) {
V put = mKVMap.put(key, value);
mVKMap.put(value, key);
return put;
}
@Override
public V remove(Object key) {
V remove = mKVMap.remove(key);
if (remove != null) {
mVKMap.remove(key);
}
return remove;
}
@Override
public void putAll(@NonNull Map<? extends K, ? extends V> m) {
mKVMap.putAll(m);
}
@Override
public void clear() {
mKVMap.clear();
mVKMap.clear();
}
@NonNull
@Override
public Set<K> keySet() {
return mKVMap.keySet();
}
@Override
public K getKey(V value) {
return mVKMap.get(value);
}
@Override
public Set<V> valueSet() {
return mVKMap.keySet();
}
@NonNull
@Override
public Collection<V> values() {
return mKVMap.values();
}
@NonNull
@Override
public Set<Entry<K, V>> entrySet() {
return mKVMap.entrySet();
}
@Override
public boolean equals(Object o) {
return mKVMap.equals(o);
}
@Override
public int hashCode() {
return mKVMap.hashCode();
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public V getOrDefault(Object key, V defaultValue) {
return mKVMap.getOrDefault(key, defaultValue);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
mKVMap.forEach(action);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
mKVMap.replaceAll(function);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public V putIfAbsent(K key, V value) {
return mKVMap.putIfAbsent(key, value);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean remove(Object key, Object value) {
return mKVMap.remove(key, value);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean replace(K key, V oldValue, V newValue) {
return mKVMap.replace(key, oldValue, newValue);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public V replace(K key, V value) {
return mKVMap.replace(key, value);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return mKVMap.computeIfAbsent(key, mappingFunction);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return mKVMap.computeIfPresent(key, remappingFunction);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return mKVMap.compute(key, remappingFunction);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return mKVMap.merge(key, value, remappingFunction);
}
}
}

View File

@@ -2,30 +2,29 @@ package com.stardust.util;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* Created by Stardust on 2017/1/26.
*/
public class MapEntries<K, V> {
public class MapBuilder<K, V> {
private Map<K, V> mMap;
public MapEntries() {
public MapBuilder() {
this(new HashMap<K, V>());
}
public MapEntries(Map<K, V> map) {
public MapBuilder(Map<K, V> map) {
mMap = map;
}
public MapEntries<K, V> entry(K key, V value) {
public MapBuilder<K, V> put(K key, V value) {
mMap.put(key, value);
return this;
}
public Map<K, V> map() {
public Map<K, V> build() {
return mMap;
}