Add: floating window, layout inspector

This commit is contained in:
hyb1996
2017-03-12 23:10:58 +08:00
parent a211d208d0
commit e81b661974
164 changed files with 7045 additions and 1206 deletions

View File

@@ -0,0 +1,10 @@
package com.stardust;
/**
* Created by Stardust on 2017/3/8.
*/
public class Test {
}

View File

@@ -6,16 +6,17 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
xmlns:tools="http://schemas.android.com/tools"
android:name=".App"
android:allowBackup="true"
android:allowBackup="false"
android:icon="@drawable/ic_android_eat_js"
android:label="@string/_app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:label">
tools:replace="android:label, android:icon, android:allowBackup">
<activity
android:name=".ui.main.MainActivity"
android:label="@string/_app_name"
@@ -53,17 +54,6 @@
</service>
<service
android:name=".external.tile.BoundsAssistEnableTileService"
android:icon="@drawable/ic_robot_head"
android:label="脚本辅助"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action
android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.stardust.scriptdroid.fileprovider"
@@ -89,6 +79,9 @@
<service android:name=".external.notification.bounds_assist.BoundsAssistSwitchNotificationHandleService"/>
<service android:name=".external.notification.record.ActionRecordSwitchHandleService"/>
<service android:name=".service.VolumeChangeListenService"/>
<service android:name="com.stardust.view.Floaty$FloatHeadService"/>
<service android:name="com.stardust.view.ResizableFloaty$FloatingWindowService"/>
<service android:name="com.stardust.scriptdroid.external.floating_window.HoverMenuService"/>
<activity
android:name=".external.open.EditIntentActivity"

View File

@@ -52,9 +52,7 @@ var longClick = function(a, b, c, d){
}, arguments);
}
var clickId = function(id){
return droid.click(droid.id(id));
}
var scrollUp = function(a, b, c, d){
if(arguments.length == 0)
@@ -222,6 +220,34 @@ var Text = function(text){
return shell("input text " + text, true).code == 1;
}
var __selector__ = droid.selector();
var __obj__ = new java.lang.Object();
for(var x in __selector__){
if(!__obj__[x] && !this[x]){
this[x] = (function(method) {
return function(){
var s = droid.selector();
if(arguments.length == 0){
return s[method]();
}else if(arguments.length == 1){
return s[method](arguments[0]);
}else if(arguments.length == 2){
return s[method](arguments[0], arguments[1]);
}else{
return s[method].call(s, Array.prototype.slice.call(arguments));
}
};
})(x);
}
}
var $ = function(){
return droid.selector();
}
__importClassOld__(com.stardust.scriptdroid.droid.runtime.Shell);
/*

View File

@@ -1,6 +1,5 @@
package com.stardust.app;
import android.app.Activity;
import android.content.Intent;
import android.util.SparseArray;

View File

@@ -0,0 +1,71 @@
package com.stardust.hover;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import android.view.WindowManager;
import io.mattcarroll.hover.HoverMenu;
import io.mattcarroll.hover.HoverMenuAdapter;
import io.mattcarroll.hover.Navigator;
import io.mattcarroll.hover.defaulthovermenu.view.ViewHoverMenu;
/**
* Created by Stardust on 2017/3/11.
*/
public class HoverMenuBuilder {
public static final int DISPLAY_MODE_WINDOW = 1; // Display directly in a window.
public static final int DISPLAY_MODE_VIEW = 2; // Display within View hierarchy.
private Context mContext;
private int mDisplayMode = DISPLAY_MODE_WINDOW;
private WindowManager mWindowManager;
private Navigator mNavigator;
private HoverMenuAdapter mAdapter;
private String mSavedVisualState = null;
public HoverMenuBuilder(@NonNull Context context) {
mContext = context;
}
public HoverMenuBuilder displayWithinWindow() {
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mDisplayMode = DISPLAY_MODE_WINDOW;
return this;
}
public HoverMenuBuilder displayWithinView(@NonNull ViewGroup container) {
mDisplayMode = DISPLAY_MODE_VIEW;
return this;
}
public HoverMenuBuilder useNavigator(@Nullable Navigator navigator) {
mNavigator = navigator;
return this;
}
public HoverMenuBuilder useAdapter(@Nullable HoverMenuAdapter adapter) {
mAdapter = adapter;
return this;
}
public HoverMenuBuilder restoreVisualState(@NonNull String visualState) {
mSavedVisualState = visualState;
return this;
}
public HoverMenu build() {
if (DISPLAY_MODE_WINDOW == mDisplayMode) {
WindowHoverMenu windowHoverMenu = new WindowHoverMenu(mContext, mWindowManager, mNavigator, mSavedVisualState);
windowHoverMenu.setAdapter(mAdapter);
return windowHoverMenu;
} else {
ViewHoverMenu viewHoverMenu = new ViewHoverMenu(mContext);
viewHoverMenu.setAdapter(mAdapter);
return viewHoverMenu;
}
}
}

View File

@@ -0,0 +1,129 @@
package com.stardust.hover;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
import io.mattcarroll.hover.HoverMenu;
import io.mattcarroll.hover.HoverMenuAdapter;
import io.mattcarroll.hover.Navigator;
/**
* Created by Stardust on 2017/3/11.
*/
public abstract class HoverMenuService extends Service {
private static final String TAG = "HoverMenuService";
private static final String PREF_FILE = "hover_menu";
private static final String PREF_HOVER_MENU_VISUAL_STATE = "hover_menu_visual_state";
private HoverMenu mHoverMenu;
private boolean mIsRunning;
private SharedPreferences mPrefs;
private HoverMenu.OnExitListener mWindowHoverMenuMenuExitListener = new HoverMenu.OnExitListener() {
@Override
public void onExitByUserRequest() {
Log.d(TAG, "Menu exit requested.");
savePreferredLocation();
mHoverMenu.hide();
onHoverMenuExitingByUserRequest();
stopSelf();
}
};
@Override
public void onCreate() {
Log.d(TAG, "onCreate()");
mPrefs = getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
mHoverMenu = new HoverMenuBuilder(getContextForHoverMenu())
.displayWithinWindow()
.useNavigator(createNavigator())
.useAdapter(createHoverMenuAdapter())
.restoreVisualState(loadPreferredLocation())
.build();
mHoverMenu.addOnExitListener(mWindowHoverMenuMenuExitListener);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!mIsRunning) {
Log.d(TAG, "onStartCommand() - showing Hover menu.");
mIsRunning = true;
mHoverMenu.show();
}
return START_STICKY;
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()");
mHoverMenu.hide();
mIsRunning = false;
}
public HoverMenu getHoverMenu() {
return mHoverMenu;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Hook for subclasses to return a custom Context to be used in the creation of the {@code HoverMenu}.
* For example, subclasses might choose to provide a ContextThemeWrapper.
*
* @return context for HoverMenu initialization
*/
protected Context getContextForHoverMenu() {
return this;
}
/**
* Subclasses can use this hook method to return a customized {@link Navigator} to be used
* throughout the entire Hover menu. This {@link Navigator} will be used for every tab in the
* Hover menu, so only supply a {@code Navigator} if you truly want every screen to display it.
* <p>
* If you want only a portion of the screens in the Hover menu to look different, then consider
* using composition of {@link NavigatorContent} to achieve the desired effect. For example,
* if you want a {@code Toolbar} to appear on one or more screens, consider placing your content
* within a {@link ToolbarNavigatorContent}, and then add the {@code ToolbarNavigatorContent}
* as the content of the default {@code Navigator}.
*
* @return Custom Navigator to use on every screen in the Hover menu
*/
protected Navigator createNavigator() {
return null; // Subclasses can override this to provide a custom Navigator.
}
abstract protected HoverMenuAdapter createHoverMenuAdapter();
/**
* Hook method for subclasses to take action when the user exits the HoverMenu. This method runs
* just before this {@code HoverMenuService} calls {@code stopSelf()}.
*/
protected void onHoverMenuExitingByUserRequest() {
// Hook for subclasses.
}
private void savePreferredLocation() {
String memento = mHoverMenu.getVisualState();
mPrefs.edit().putString(PREF_HOVER_MENU_VISUAL_STATE, memento).apply();
}
private String loadPreferredLocation() {
return mPrefs.getString(PREF_HOVER_MENU_VISUAL_STATE, null);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
package com.stardust.hover;
import io.mattcarroll.hover.defaulthovermenu.*;
import io.mattcarroll.hover.defaulthovermenu.HoverMenuView;
/**
* Created by Stardust on 2017/3/12.
*/
public class SimpleHoverMenuTransitionListener implements HoverMenuView.HoverMenuTransitionListener {
@Override
public void onCollapsing() {
}
@Override
public void onCollapsed() {
}
@Override
public void onExpanding() {
}
@Override
public void onExpanded() {
}
}

View File

@@ -0,0 +1,254 @@
package com.stardust.hover;
import android.content.Context;
import android.graphics.PointF;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashSet;
import java.util.Set;
import io.mattcarroll.hover.HoverMenu;
import io.mattcarroll.hover.HoverMenuAdapter;
import io.mattcarroll.hover.Navigator;
import io.mattcarroll.hover.defaulthovermenu.HoverMenuView;
import io.mattcarroll.hover.defaulthovermenu.window.InWindowDragger;
import io.mattcarroll.hover.defaulthovermenu.window.WindowViewController;
/**
* Created by Stardust on 2017/3/11.
*/
public class WindowHoverMenu implements HoverMenu {
private HoverMenuView.HoverMenuTransitionListener mHoverMenuTransitionListener;
private static final String TAG = "WindowHoverMenu";
public WindowViewController getWindowViewController() {
return mWindowViewController;
}
private WindowViewController mWindowViewController; // Shows/hides/positions Views in a Window.
private HoverMenuView mHoverMenuView; // The visual presentation of the Hover menu.
private boolean mIsShowingHoverMenu; // Are we currently display mHoverMenuView?
private boolean mIsInDragMode; // If we're not in drag mode then we're in menu mode.
private Set<OnExitListener> mOnExitListeners = new HashSet<>();
private HoverMenuView.HoverMenuTransitionListener mHoverMenuTransitionListenerProxy = new HoverMenuView.HoverMenuTransitionListener() {
@Override
public void onCollapsing() {
if (mHoverMenuTransitionListener != null)
mHoverMenuTransitionListener.onCollapsing();
}
@Override
public void onCollapsed() {
mIsInDragMode = true;
// When collapsed, we make mHoverMenuView untouchable so that the WindowDragWatcher can
// take over. We do this so that touch events outside the drag area can propagate to
// applications on screen.
mWindowViewController.makeUntouchable(mHoverMenuView);
if (mHoverMenuTransitionListener != null)
mHoverMenuTransitionListener.onCollapsed();
}
@Override
public void onExpanding() {
mIsInDragMode = false;
if (mHoverMenuTransitionListener != null)
mHoverMenuTransitionListener.onExpanding();
}
@Override
public void onExpanded() {
mWindowViewController.makeTouchable(mHoverMenuView);
if (mHoverMenuTransitionListener != null)
mHoverMenuTransitionListener.onExpanded();
}
};
private HoverMenuView.HoverMenuExitRequestListener mMenuExitRequestListener = new HoverMenuView.HoverMenuExitRequestListener() {
@Override
public void onExitRequested() {
hide();
}
};
public WindowHoverMenu(@NonNull Context context, @NonNull WindowManager windowManager, @Nullable Navigator navigator, @Nullable String savedVisualState) {
mWindowViewController = new WindowViewController(windowManager);
InWindowDragger inWindowDragger = new InWindowDragger(
context,
mWindowViewController,
ViewConfiguration.get(context).getScaledTouchSlop()
);
PointF anchorState = new PointF(2, 0.5f); // Default to right side, half way down. See CollapsedMenuAnchor.
if (null != savedVisualState) {
try {
VisualStateMemento visualStateMemento = VisualStateMemento.fromJsonString(savedVisualState);
anchorState.set(visualStateMemento.getAnchorSide(), visualStateMemento.getNormalizedPositionY());
} catch (JSONException e) {
e.printStackTrace();
}
}
mHoverMenuView = new HoverMenuView(context, navigator, inWindowDragger, anchorState);
mHoverMenuView.setHoverMenuExitRequestListener(mMenuExitRequestListener);
mHoverMenuView.setHoverMenuTransitionListener(mHoverMenuTransitionListenerProxy);
}
public void setHoverMenuTransitionListener(HoverMenuView.HoverMenuTransitionListener hoverMenuTransitionListener) {
mHoverMenuTransitionListener = hoverMenuTransitionListener;
}
public HoverMenuView getHoverMenuView() {
return mHoverMenuView;
}
@Override
public String getVisualState() {
PointF anchor = mHoverMenuView.getAnchorState();
return new VisualStateMemento((int) anchor.x, anchor.y).toJsonString();
}
@Override
public void restoreVisualState(@NonNull String savedVisualState) {
try {
VisualStateMemento memento = VisualStateMemento.fromJsonString(savedVisualState);
mHoverMenuView.setAnchorState(new PointF(memento.getAnchorSide(), memento.getNormalizedPositionY()));
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void setAdapter(@Nullable HoverMenuAdapter adapter) {
mHoverMenuView.setAdapter(adapter);
}
/**
* Initializes and displays the Hover menu. To destroy and remove the Hover menu, use {@link #hide()}.
*/
@Override
public void show() {
if (!mIsShowingHoverMenu) {
mWindowViewController.addView(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, false, mHoverMenuView);
// Sync our control state with the HoverMenuView state.
if (mHoverMenuView.isExpanded()) {
mWindowViewController.makeTouchable(mHoverMenuView);
} else {
collapseMenu();
}
mIsShowingHoverMenu = true;
}
}
/**
* Exits the Hover menu system. This method is the inverse of {@link #show()}.
*/
@Override
public void hide() {
if (mIsShowingHoverMenu) {
mIsShowingHoverMenu = false;
// Notify our exit listeners that we're exiting.
notifyOnExitListeners();
// Cleanup the control structures and Views.
mWindowViewController.removeView(mHoverMenuView);
mHoverMenuView.release();
}
}
/**
* Expands the Hover menu to show all of its tabs and a content area for the selected tab. To
* collapse the menu down a single active tab, use {@link #collapseMenu()}.
*/
@Override
public void expandMenu() {
if (mIsInDragMode) {
mHoverMenuView.expand();
}
}
/**
* Collapses the Hover menu down to its single active tab and allows the tab to be dragged
* around the display. This method is the inverse of {@link #expandMenu()}.
*/
@Override
public void collapseMenu() {
if (!mIsInDragMode) {
mHoverMenuView.collapse();
}
}
@Override
public void addOnExitListener(@NonNull OnExitListener onExitListener) {
mOnExitListeners.add(onExitListener);
}
@Override
public void removeOnExitListener(@NonNull OnExitListener onExitListener) {
mOnExitListeners.remove(onExitListener);
}
private void notifyOnExitListeners() {
for (OnExitListener listener : mOnExitListeners) {
listener.onExitByUserRequest();
}
}
private static class VisualStateMemento {
private static final String JSON_KEY_ANCHOR_SIDE = "anchor_side";
private static final String JSON_KEY_NORMALIZED_POSITION_Y = "normalized_position_y";
public static VisualStateMemento fromJsonString(@NonNull String jsonString) throws JSONException {
JSONObject jsonObject = new JSONObject(jsonString);
int anchorSide = jsonObject.getInt(JSON_KEY_ANCHOR_SIDE);
float normalizedPositionY = (float) jsonObject.getDouble(JSON_KEY_NORMALIZED_POSITION_Y);
return new VisualStateMemento(anchorSide, normalizedPositionY);
}
private int mAnchorSide;
private float mNormalizedPositionY;
public VisualStateMemento(int anchorSide, float normalizedPositionY) {
mAnchorSide = anchorSide;
mNormalizedPositionY = normalizedPositionY;
}
public int getAnchorSide() {
return mAnchorSide;
}
public float getNormalizedPositionY() {
return mNormalizedPositionY;
}
public String toJsonString() {
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put(JSON_KEY_ANCHOR_SIDE, mAnchorSide);
jsonObject.put(JSON_KEY_NORMALIZED_POSITION_Y, mNormalizedPositionY);
return jsonObject.toString();
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
}
}

View File

@@ -2,20 +2,28 @@ package com.stardust.scriptdroid;
import android.app.Activity;
import android.app.Application;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextPaint;
import android.util.Log;
import com.squareup.leakcanary.LeakCanary;
import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
import com.stardust.scriptdroid.layout_inspector.LayoutInspector;
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.squareup.leakcanary.LeakCanary;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate;
import com.stardust.scriptdroid.tool.ViewTool;
import com.stardust.scriptdroid.ui.error.ErrorReportActivity;
import com.stardust.theme.ThemeColorManager;
import com.stardust.util.CrashHandler;
import com.stardust.util.StateObserver;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* Created by Stardust on 2017/1/27.
@@ -23,6 +31,8 @@ import java.lang.ref.WeakReference;
public class App extends Application {
private static final String TAG = "App";
private static WeakReference<App> instance;
private static StateObserver stateObserver;
private static WeakReference<Activity> currentActivity;
@@ -49,14 +59,15 @@ public class App extends Application {
stateObserver = new StateObserver(PreferenceManager.getDefaultSharedPreferences(this));
registerActivityLifecycleCallback();
initAccessibilityServiceDelegates();
}
private void initAccessibilityServiceDelegates() {
AccessibilityWatchDogService.addDelegateIfNeeded(100, ActionPerformAccessibilityDelegate.class);
AccessibilityWatchDogService.addDelegateIfNeeded(200, AccessibilityRecorderDelegate.getInstance());
AccessibilityWatchDogService.addDelegateIfNeeded(300, BoundsAssistant.class);
AccessibilityWatchDogService.addDelegateIfNeeded(300, AccessibilityEventCommandHost.instance);
AccessibilityWatchDogService.addDelegateIfNeeded(400, AccessibilityInfoProvider.instance);
AccessibilityWatchDogService.addDelegateIfNeeded(500, LayoutInspector.getInstance());
}
@@ -85,6 +96,10 @@ public class App extends Application {
return currentActivity.get();
}
public static String getResString(int id) {
return getApp().getString(id);
}
private static class SimpleActivityLifecycleCallbacks implements ActivityLifecycleCallbacks {

View File

@@ -0,0 +1,51 @@
package com.stardust.scriptdroid.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.scriptdroid.App;
import com.stardust.view.accessibility.AccessibilityDelegate;
/**
* Created by Stardust on 2017/3/9.
*/
public class AccessibilityInfoProvider implements AccessibilityDelegate {
public static AccessibilityInfoProvider instance = new AccessibilityInfoProvider();
private String mLatestPackage = "";
private String mLatestActivity = "";
public synchronized String getLatestPackage() {
return mLatestPackage;
}
public synchronized String getLatestActivity() {
return mLatestActivity;
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
setLatestComponent(event.getPackageName(), event.getClassName());
return false;
}
private synchronized void setLatestComponent(CharSequence latestPackage, CharSequence latestClass) {
if (latestPackage == null)
return;
mLatestPackage = latestPackage.toString();
if (latestClass == null)
return;
try {
ComponentName componentName = new ComponentName(latestPackage.toString(), latestClass.toString());
ActivityInfo activityInfo = App.getApp().getPackageManager().getActivityInfo(componentName, 0);
mLatestActivity = activityInfo.name;
} catch (PackageManager.NameNotFoundException ignored) {
}
}
}

View File

@@ -1,25 +0,0 @@
package com.stardust.scriptdroid.bounds_assist;
/**
* Created by Stardust on 2017/2/4.
*/
public interface BoundsAssistClipList {
interface OnClipChangedListener {
void onClipRemove(int position);
void onClipInsert(int position);
void onChange();
}
int size();
String get(int i);
void add(String clip);
void setOnClipChangedListener(OnClipChangedListener listener);
}

View File

@@ -1,93 +0,0 @@
package com.stardust.scriptdroid.bounds_assist;
import android.accessibilityservice.AccessibilityService;
import android.graphics.Rect;
import android.preference.PreferenceManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.service.AccessibilityDelegate;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
/**
* Created by Stardust on 2017/2/4.
*/
public class BoundsAssistant implements AccessibilityDelegate {
public static final String KEY_BOUNDS_ASSIST_ENABLE = "ASSIST_MODE_ENABLE";
private static boolean assistModeEnable;
private static BoundsAssistClipList boundsAssistClipList = SharedPrefBoundsAssistClipList.getInstance();
public static boolean isAssistModeEnable() {
return assistModeEnable;
}
public static void setAssistModeEnable(boolean assistModeEnable) {
if (assistModeEnable && Pref.isFirstEnableAssistMode()) {
showAssistModeInfoDialog();
}
BoundsAssistant.assistModeEnable = assistModeEnable;
App.getStateObserver().setState(KEY_BOUNDS_ASSIST_ENABLE, assistModeEnable);
}
private static void showAssistModeInfoDialog() {
new ThemeColorMaterialDialogBuilder(App.currentActivity())
.title(R.string.text_alert)
.content(R.string.assist_mode_notice)
.positiveText(R.string.ok)
.show();
}
private static void performAssistance(AccessibilityEvent event) {
if (!assistModeEnable) {
return;
}
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED || event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo == null) {
return;
}
nodeInfo.refresh();
Rect rect = getBoundsInScreen(nodeInfo);
saveAndAlertBounds(rect);
nodeInfo.recycle();
}
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
performAssistance(event);
return false;
}
public static Rect getBoundsInScreen(AccessibilityNodeInfo nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
return rect;
}
private static void saveAndAlertBounds(Rect rect) {
String str = boundsToString(rect);
boundsAssistClipList.add(str);
Toast.makeText(App.getApp(), str, Toast.LENGTH_SHORT).show();
}
public static String boundsToString(Rect rect) {
return rect.toString().replace('-', ',').replace(" ", "").substring(4);
}
static {
assistModeEnable = PreferenceManager.getDefaultSharedPreferences(App.getApp()).getBoolean(KEY_BOUNDS_ASSIST_ENABLE, false);
}
}

View File

@@ -1,89 +0,0 @@
package com.stardust.scriptdroid.bounds_assist;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.stardust.scriptdroid.App;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;
/**
* Created by Stardust on 2017/2/4.
*/
public class SharedPrefBoundsAssistClipList implements BoundsAssistClipList {
private static final Gson GSON = new Gson();
private static SharedPrefBoundsAssistClipList instance = new SharedPrefBoundsAssistClipList(App.getApp());
private OnClipChangedListener mOnClipChangedListener;
public static SharedPrefBoundsAssistClipList getInstance() {
return instance;
}
private static final int MAX_SIZE = 10;
private static final String SHARED_PREF_NAME = "SharedPrefBoundsAssistClipList";
private SharedPreferences mSharedPreferences;
private List<String> mAssistClipList = new LinkedList<>();
public SharedPrefBoundsAssistClipList(Context context) {
mSharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
readFromSharedPref();
}
private void readFromSharedPref() {
Type type = new TypeToken<List<String>>() {
}.getType();
List<String> l = GSON.fromJson(mSharedPreferences.getString(SHARED_PREF_NAME, ""), type);
if (l != null)
mAssistClipList.addAll(l);
}
@Override
public synchronized int size() {
return mAssistClipList.size();
}
@Override
public synchronized String get(int i) {
return mAssistClipList.get(i);
}
@Override
public synchronized void add(String clip) {
mAssistClipList.add(0, clip);
notifyInsert();
if (mAssistClipList.size() > MAX_SIZE) {
mAssistClipList.remove(mAssistClipList.size() - 1);
notifyRemove();
}
syncWithSharedPref();
}
private void notifyRemove() {
if (mOnClipChangedListener != null)
mOnClipChangedListener.onClipRemove(mAssistClipList.size());
}
private void notifyInsert() {
if (mOnClipChangedListener != null) {
mOnClipChangedListener.onClipInsert(0);
}
}
@Override
public void setOnClipChangedListener(OnClipChangedListener listener) {
mOnClipChangedListener = listener;
}
private void syncWithSharedPref() {
mSharedPreferences.edit().putString(SHARED_PREF_NAME, GSON.toJson(mAssistClipList)).apply();
}
}

View File

@@ -16,21 +16,21 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.jraska.console.Console;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.runtime.action.Action;
import com.stardust.scriptdroid.droid.runtime.action.ActionFactory;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
import com.stardust.scriptdroid.droid.runtime.action.GetTextAction;
import com.stardust.scriptdroid.file.FileUtils;
import com.stardust.scriptdroid.service.AccessibilityDelegate;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.tool.Shell;
import com.stardust.scriptdroid.ui.console.ConsoleActivity;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import java.io.File;
@@ -194,7 +194,7 @@ public class DroidRuntime {
errorMessage = App.getApp().getString(R.string.text_auto_operate_service_enabled_but_not_running);
} else {
if (Pref.def().getBoolean(App.getApp().getString(R.string.key_enable_accessibility_service_by_root), false)) {
if (!AccessibilityServiceUtils.enableAccessibilityServiceByRootAndWaitFor(App.getApp(), AccessibilityWatchDogService.class, 3000)) {
if (!AccessibilityServiceTool.enableAccessibilityServiceByRootAndWaitFor(AccessibilityWatchDogService.class, 3000)) {
errorMessage = App.getApp().getString(R.string.text_enable_accessibility_service_by_root_timeout);
}
} else {
@@ -208,10 +208,6 @@ public class DroidRuntime {
}
}
public List<String> getTexts() {
return performAction(new GetTextAction());
}
public void toast(final String text) {
mUIHandler.post(new Runnable() {
@Override
@@ -229,16 +225,10 @@ public class DroidRuntime {
}
}
public void addAccessibilityDelegate(final Object delegate) {
public void addAccessibilityEventListener(final Object delegate) {
if (delegate == null)
return;
AccessibilityWatchDogService.addDelegate(new AccessibilityDelegate() {
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
return false;
}
}, 500);
}
public Shell.CommandResult shell(String cmd, int root) {
@@ -255,6 +245,10 @@ public class DroidRuntime {
return ActionPerformAccessibilityDelegate.getLatestActivity();
}
public UiSelector selector() {
return new UiSelector();
}
public boolean isStopped() {
return Thread.currentThread().isInterrupted();
}

View File

@@ -0,0 +1,228 @@
package com.stardust.scriptdroid.droid.runtime;
import android.accessibilityservice.AccessibilityService;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.bezyapps.floatieslibrary.Floaty;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.automator.ActionArgument;
import com.stardust.automator.UiGlobalSelector;
import com.stardust.automator.UiObject;
import com.stardust.automator.UiObjectCollection;
import com.stardust.automator.filter.DfsFilter;
import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_COLUMN_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_PROGRESS_VALUE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_ROW_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_END_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SELECTION_START_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_COLLAPSE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_COPY;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CUT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_DISMISS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_EXPAND;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_LONG_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_PASTE;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SELECT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SET_SELECTION;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_SET_TEXT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CONTEXT_CLICK;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_DOWN;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_LEFT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_TO_POSITION;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_PROGRESS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SHOW_ON_SCREEN;
/**
* Created by Stardust on 2017/3/9.
*/
public class UiSelector extends UiGlobalSelector {
private class FindCommand implements AccessibilityEventCommandHost.Command {
UiObjectCollection result;
@Override
public void execute(AccessibilityService service, AccessibilityEvent event) {
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root != null) {
result = findOf(root);
}
}
}
private static final String TAG = "UiSelector";
public UiObjectCollection find() {
FindCommand command = new FindCommand();
AccessibilityEventCommandHost.instance.executeAndWaitForEvent(command);
return command.result;
}
@NonNull
public UiObjectCollection untilFind() {
UiObjectCollection uiObjectCollection;
do {
uiObjectCollection = find();
} while (uiObjectCollection == null || uiObjectCollection.size() == 0);
return uiObjectCollection;
}
public UiObject findOne() {
UiObjectCollection collection = find();
if (collection == null || collection.size() == 0) {
return null;
}
return new UiObject(collection.get(0).getInfo());
}
@NonNull
public UiObject untilFindOne() {
UiObjectCollection collection = untilFind();
return new UiObject(collection.get(0).getInfo());
}
public UiSelector id(final String id) {
if (!id.contains(":")) {
addFilter(new DfsFilter() {
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String fullId = AccessibilityInfoProvider.instance.getLatestPackage() + ":id/" + id;
return fullId.equals(nodeInfo.getViewIdResourceName());
}
});
} else {
super.id(id);
}
return this;
}
public boolean performAction(int action, ActionArgument... arguments) {
return untilFind().performAction(action, arguments);
}
public boolean click() {
return performAction(ACTION_CLICK);
}
public boolean longClick() {
return performAction(ACTION_LONG_CLICK);
}
public boolean accessibilityFocus() {
return performAction(ACTION_ACCESSIBILITY_FOCUS);
}
public boolean clearAccessibilityFocus() {
return performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
public boolean focus() {
return performAction(ACTION_FOCUS);
}
public boolean clearFocus() {
return performAction(ACTION_CLEAR_FOCUS);
}
public boolean copy() {
return performAction(ACTION_COPY);
}
public boolean paste() {
return performAction(ACTION_PASTE);
}
public boolean select() {
return performAction(ACTION_SELECT);
}
public boolean cut() {
return performAction(ACTION_CUT);
}
public boolean collapse() {
return performAction(ACTION_COLLAPSE);
}
public boolean expand() {
return performAction(ACTION_EXPAND);
}
public boolean dismiss() {
return performAction(ACTION_DISMISS);
}
public boolean show() {
return performAction(ACTION_SHOW_ON_SCREEN.getId());
}
public boolean scrollForward() {
return performAction(ACTION_SCROLL_FORWARD);
}
public boolean scrollBackward() {
return performAction(ACTION_SCROLL_BACKWARD);
}
public boolean scrollUp() {
return performAction(ACTION_SCROLL_UP.getId());
}
public boolean scrollDown() {
return performAction(ACTION_SCROLL_DOWN.getId());
}
public boolean scrollLeft() {
return performAction(ACTION_SCROLL_LEFT.getId());
}
public boolean scrollRight() {
return performAction(ACTION_SCROLL_RIGHT.getId());
}
public boolean contextClick() {
return performAction(ACTION_CONTEXT_CLICK.getId());
}
public boolean setSelection(int s, int e) {
return performAction(ACTION_SET_SELECTION,
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_SELECTION_START_INT, s),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_SELECTION_END_INT, e));
}
public boolean setText(String text) {
return performAction(ACTION_SET_TEXT,
new ActionArgument.CharSequenceActionArgument(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text));
}
public boolean setProgress(float value) {
return performAction(ACTION_SET_PROGRESS.getId(),
new ActionArgument.FloatActionArgument(ACTION_ARGUMENT_PROGRESS_VALUE, value));
}
public boolean scrollTo(int row, int column) {
return performAction(ACTION_SCROLL_TO_POSITION.getId(),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_ROW_INT, row),
new ActionArgument.IntActionArgument(ACTION_ARGUMENT_COLUMN_INT, column));
}
}

View File

@@ -9,7 +9,6 @@ import com.stardust.util.SparseArrayEntries;
* Created by Stardust on 2017/1/27.
*/
@FunctionalInterface
public interface Able {
SparseArray<Able> ABLE_MAP = new SparseArrayEntries<Able>()

View File

@@ -10,7 +10,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.service.AccessibilityDelegate;
import com.stardust.view.accessibility.AccessibilityDelegate;
/**
@@ -50,6 +50,7 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root == null) {
Log.v(TAG, "root = null");
return false;
}
Log.i(TAG, "perform action:" + action);
if (action.perform(root)) {

View File

@@ -33,8 +33,7 @@ public abstract class JavaScriptEngine {
try {
return FileUtils.readString(App.getApp().getAssets().open("javasccript_engine_init.js"));
} catch (IOException e) {
e.printStackTrace();
return "Error: Unable to read init script.";
throw new RuntimeException(e);
}
}

View File

@@ -1,7 +1,7 @@
package com.stardust.scriptdroid.droid.script;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.droid.runtime.ScriptStopException;
@@ -58,10 +58,10 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
private void init(Context context, Scriptable scope) {
ScriptableObject.putProperty(scope, "context", App.getApp());
ScriptableObject.putProperty(scope, "__engine__", "rhino");
context.evaluateString(scope, Init.getInitScript(), "<init>", 1, null);
for (Map.Entry<String, Object> variable : mVariableMap.entrySet()) {
ScriptableObject.putProperty(scope, variable.getKey(), variable.getValue());
}
context.evaluateString(scope, Init.getInitScript(), "<init>", 1, null);
mThreads.add(Thread.currentThread());
}

View File

@@ -8,8 +8,6 @@ import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.droid.RunningConfig;
import static com.stardust.scriptdroid.droid.Droid.JAVA_SCRIPT_ENGINE;
/**
* Created by Stardust on 2017/2/5.
*/
@@ -43,9 +41,9 @@ public class ScriptExecuteActivity extends Activity {
}
private void runScript() {
JAVA_SCRIPT_ENGINE.set("activity", this);
Droid.JAVA_SCRIPT_ENGINE.set("activity", this);
try {
mResult = JAVA_SCRIPT_ENGINE.execute(mScript);
mResult = Droid.JAVA_SCRIPT_ENGINE.execute(mScript);
} catch (Exception e) {
mOnRunFinishedListener.onException(e);
finish();
@@ -61,7 +59,7 @@ public class ScriptExecuteActivity extends Activity {
@Override
protected void onDestroy() {
super.onDestroy();
JAVA_SCRIPT_ENGINE.set("activity", null);
JAVA_SCRIPT_ENGINE.removeAndDestroy();
Droid.JAVA_SCRIPT_ENGINE.set("activity", null);
Droid.JAVA_SCRIPT_ENGINE.removeAndDestroy();
}
}

View File

@@ -2,9 +2,9 @@ package com.stardust.scriptdroid.droid.script.file;
import android.os.Environment;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.Droid;
import java.io.File;

View File

@@ -0,0 +1,32 @@
package com.stardust.scriptdroid.external.floating_window;
import android.content.Intent;
import android.view.View;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.layout_inspector.view.LayoutInspectView;
import com.stardust.view.Floaty;
import com.stardust.view.ResizableFloaty;
/**
* Created by Stardust on 2017/3/10.
*/
public class FloatingWindowManger {
public static void showFloatingWindow() {
if (!HoverMenuService.isServiceRunning())
App.getApp().startService(new Intent(App.getApp(), HoverMenuService.class));
}
public static boolean isFloatingWindowShowing() {
return HoverMenuService.isServiceRunning();
}
public static void hideFloatingWindow() {
if (HoverMenuService.isServiceRunning())
HoverMenuService.stopService();
}
}

View File

@@ -0,0 +1,214 @@
package com.stardust.scriptdroid.external.floating_window;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.TypedValue;
import android.view.View;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floating_window.content.MainMenuNavigatorContent;
import com.stardust.scriptdroid.external.floating_window.content.RecordNavigatorContent;
import com.stardust.scriptdroid.external.floating_window.content.ScritpListNavigatorContent;
import com.stardust.theme.ThemeColorManagerCompat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.mattcarroll.hover.NavigatorContent;
/**
* Created by Stardust on 2017/3/11.
*/
public class HoverMenuAdapter implements io.mattcarroll.hover.HoverMenuAdapter {
public static final String ID_MAIN = "intro";
public static final String ID_RECORD = "record";
public static final String ID_SCRIPT_LIST = "script_list";
public static final String MENU_ID = "menu";
public static final String PLACEHOLDER_ID = "placeholder";
private final Context mContext;
private final List<String> mTabIds;
private final Map<String, NavigatorContent> mData = new LinkedHashMap<>();
private final Set<ContentChangeListener> mContentChangeListeners = new HashSet<>();
private View[] mViews;
public HoverMenuAdapter(@NonNull Context context) {
mContext = context;
mData.put(HoverMenuAdapter.ID_MAIN, new MainMenuNavigatorContent(context));
mData.put(HoverMenuAdapter.ID_RECORD, new RecordNavigatorContent(context));
mData.put(HoverMenuAdapter.ID_SCRIPT_LIST, new ScritpListNavigatorContent(context));
mTabIds = new ArrayList<>();
for (String tabId : mData.keySet()) {
mTabIds.add(tabId);
}
mViews = new View[mTabIds.size()];
}
@Override
public int getTabCount() {
return mTabIds.size();
}
@Override
public View getTabView(int index) {
mViews[index] = getTabViewInner(index);
return mViews[index];
}
private View getTabViewInner(int index) {
String menuItemId = mTabIds.get(index);
if (ID_MAIN.equals(menuItemId)) {
return createTabView(R.drawable.ic_android_eat_js);
} else if (ID_RECORD.equals(menuItemId)) {
return createTabView(R.drawable.ic_video_record);
} else if (ID_SCRIPT_LIST.equals(menuItemId)) {
return createTabView(R.drawable.ic_menu);
} else {
throw new RuntimeException("Unknown tab selected: " + index);
}
}
@Override
public long getTabId(int position) {
return position;
}
@Override
public NavigatorContent getNavigatorContent(int index) {
String tabId = mTabIds.get(index);
return mData.get(tabId);
}
@Override
public void addContentChangeListener(@NonNull ContentChangeListener listener) {
mContentChangeListeners.add(listener);
}
@Override
public void removeContentChangeListener(@NonNull ContentChangeListener listener) {
mContentChangeListeners.remove(listener);
}
public void selectTab(String id) {
int i = mTabIds.indexOf(id);
mViews[i].performClick();
}
protected void notifyDataSetChanged() {
for (ContentChangeListener listener : mContentChangeListeners) {
listener.onContentChange(this);
}
}
private View createTabView(@DrawableRes int tabBitmapRes) {
return createTabView(tabBitmapRes, ThemeColorManagerCompat.getColorPrimary(), Color.WHITE);
}
private View createTabView(@DrawableRes int tabBitmapRes, @ColorInt int backgroundColor, @ColorInt Integer iconColor) {
Resources resources = mContext.getResources();
int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, resources.getDisplayMetrics());
DemoTabView view = new DemoTabView(mContext, resources.getDrawable(R.drawable.tab_background), resources.getDrawable(tabBitmapRes));
view.setTabBackgroundColor(backgroundColor);
view.setTabForegroundColor(iconColor);
view.setPadding(padding, padding, padding, padding);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.setElevation(padding);
}
return view;
}
public class DemoTabView extends View {
private int mBackgroundColor;
private Integer mForegroundColor;
private Drawable mCircleDrawable;
private Drawable mIconDrawable;
private int mIconInsetLeft, mIconInsetTop, mIconInsetRight, mIconInsetBottom;
public DemoTabView(Context context, Drawable backgroundDrawable, Drawable iconDrawable) {
super(context);
mCircleDrawable = backgroundDrawable;
mIconDrawable = iconDrawable;
init();
}
private void init() {
int insetsDp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getContext().getResources().getDisplayMetrics());
mIconInsetLeft = mIconInsetTop = mIconInsetRight = mIconInsetBottom = insetsDp;
}
public void setTabBackgroundColor(@ColorInt int backgroundColor) {
mBackgroundColor = backgroundColor;
mCircleDrawable.setColorFilter(mBackgroundColor, PorterDuff.Mode.SRC_ATOP);
}
public void setTabForegroundColor(@ColorInt Integer foregroundColor) {
mForegroundColor = foregroundColor;
if (null != mForegroundColor) {
mIconDrawable.setColorFilter(mForegroundColor, PorterDuff.Mode.SRC_ATOP);
} else {
mIconDrawable.setColorFilter(null);
}
}
public void setIcon(@Nullable Drawable icon) {
mIconDrawable = icon;
if (null != mForegroundColor && null != mIconDrawable) {
mIconDrawable.setColorFilter(mForegroundColor, PorterDuff.Mode.SRC_ATOP);
}
updateIconBounds();
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Make circle as large as View minus padding.
mCircleDrawable.setBounds(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom());
// Re-size the icon as necessary.
updateIconBounds();
invalidate();
}
private void updateIconBounds() {
if (null != mIconDrawable) {
Rect bounds = new Rect(mCircleDrawable.getBounds());
bounds.set(bounds.left + mIconInsetLeft, bounds.top + mIconInsetTop, bounds.right - mIconInsetRight, bounds.bottom - mIconInsetBottom);
mIconDrawable.setBounds(bounds);
}
}
@Override
protected void onDraw(Canvas canvas) {
mCircleDrawable.draw(canvas);
if (null != mIconDrawable) {
mIconDrawable.draw(canvas);
}
}
}
}

View File

@@ -0,0 +1,191 @@
package com.stardust.scriptdroid.external.floating_window;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import com.stardust.hover.HoverMenuBuilder;
import com.stardust.hover.SimpleHoverMenuTransitionListener;
import com.stardust.hover.WindowHoverMenu;
import com.stardust.scriptdroid.external.floating_window.view.FloatingLayoutBoundsView;
import com.stardust.scriptdroid.external.floating_window.view.FloatingLayoutHierarchyView;
import com.stardust.scriptdroid.layout_inspector.LayoutInspector;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.theme.ThemeColorManagerCompat;
import com.stardust.util.MessageEvent;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.lang.ref.WeakReference;
import io.mattcarroll.hover.HoverMenu;
import io.mattcarroll.hover.defaulthovermenu.window.WindowViewController;
/**
* Created by Stardust on 2017/3/11.
*/
public class HoverMenuService extends Service {
public static final String MESSAGE_SHOW_AND_EXPAND_MENU = "MESSAGE_SHOW_AND_EXPAND_MENU";
public static final String MESSAGE_SHOW_LAYOUT_HIERARCHY = "MESSAGE_SHOW_LAYOUT_HIERARCHY";
public static final String MESSAGE_SHOW_LAYOUT_BOUNDS = "MESSAGE_SHOW_LAYOUT_BOUNDS";
public static final String MESSAGE_COLLAPSE_MENU = "MESSAGE_COLLAPSE_MENU";
public static final String MESSAGE_MENU_COLLAPSING = "MESSAGE_MENU_COLLAPSING";
private static WeakReference<HoverMenuService> service;
public static boolean isServiceRunning() {
return service != null && service.get() != null;
}
public static void stopService() {
if (isServiceRunning()) {
service.get().stopSelf();
}
}
private static final String TAG = "HoverMenuService";
private static final String PREF_FILE = "hover_menu";
private static final String PREF_HOVER_MENU_VISUAL_STATE = "hover_menu_visual_state";
private boolean mIsRunning;
private SharedPreferences mPrefs;
private WindowViewController mWindowViewController;
private FloatingLayoutHierarchyView mFloatingLayoutHierarchyView;
private FloatingLayoutBoundsView mFloatingLayoutBoundsView;
private WindowHoverMenu mWindowHoverMenu;
private HoverMenu.OnExitListener mWindowHoverMenuMenuExitListener = new HoverMenu.OnExitListener() {
@Override
public void onExitByUserRequest() {
savePreferredLocation();
mWindowHoverMenu.hide();
stopSelf();
}
};
@Override
public void onCreate() {
super.onCreate();
if (isServiceRunning()) {
stopSelf();
} else {
service = new WeakReference<>(this);
}
EventBus.getDefault().register(this);
mPrefs = getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
initViews();
}
private void initViews() {
mFloatingLayoutHierarchyView = new FloatingLayoutHierarchyView(this);
mFloatingLayoutBoundsView = new FloatingLayoutBoundsView(this);
initWindowMenu();
mWindowViewController.addView(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, true, mFloatingLayoutHierarchyView);
mWindowViewController.addView(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, true, mFloatingLayoutBoundsView);
}
private void initWindowMenu() {
mWindowHoverMenu = (WindowHoverMenu) new HoverMenuBuilder(this)
.displayWithinWindow()
.useAdapter(new HoverMenuAdapter(this))
.restoreVisualState(loadPreferredLocation())
.build();
mWindowHoverMenu.getHoverMenuView().setContentBackgroundColor(ThemeColorManagerCompat.getColorPrimary());
mWindowHoverMenu.addOnExitListener(mWindowHoverMenuMenuExitListener);
mWindowViewController = mWindowHoverMenu.getWindowViewController();
mWindowHoverMenu.setHoverMenuTransitionListener(new SimpleHoverMenuTransitionListener() {
@Override
public void onExpanding() {
captureCurrentWindow();
}
@Override
public void onCollapsing() {
LayoutInspector.getInstance().clearCapture();
EventBus.getDefault().post(new MessageEvent(MESSAGE_MENU_COLLAPSING));
}
});
}
private void captureCurrentWindow() {
NodeInfo nodeInfo = LayoutInspector.getInstance().captureCurrentWindow();
if (nodeInfo == null)
return;
mFloatingLayoutHierarchyView.setRootNode(nodeInfo);
mFloatingLayoutBoundsView.setRootNode(nodeInfo);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!mIsRunning) {
mIsRunning = true;
mWindowHoverMenu.show();
}
return START_STICKY;
}
@Override
public void onDestroy() {
mWindowHoverMenu.hide();
mIsRunning = false;
if (isServiceRunning() && service.get() == this)
service.clear();
}
private void savePreferredLocation() {
String memento = mWindowHoverMenu.getVisualState();
mPrefs.edit().putString(PREF_HOVER_MENU_VISUAL_STATE, memento).apply();
}
private String loadPreferredLocation() {
return mPrefs.getString(PREF_HOVER_MENU_VISUAL_STATE, null);
}
@Subscribe
public void onMessageEvent(MessageEvent event) {
switch (event.message) {
case MESSAGE_SHOW_AND_EXPAND_MENU:
mWindowHoverMenu.show();
break;
case MESSAGE_SHOW_LAYOUT_HIERARCHY:
mWindowHoverMenu.hide();
showView(mFloatingLayoutHierarchyView);
break;
case MESSAGE_SHOW_LAYOUT_BOUNDS:
mWindowHoverMenu.hide();
showView(mFloatingLayoutBoundsView);
break;
case MESSAGE_COLLAPSE_MENU:
mWindowHoverMenu.collapseMenu();
break;
}
}
private void showView(View view) {
view.setVisibility(View.VISIBLE);
mWindowViewController.makeTouchable(view);
}
}

View File

@@ -0,0 +1,82 @@
package com.stardust.scriptdroid.external.floating_window.content;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.Toast;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floating_window.HoverMenuService;
import com.stardust.scriptdroid.layout_inspector.LayoutInspector;
import com.stardust.util.MessageEvent;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.mattcarroll.hover.Navigator;
import io.mattcarroll.hover.NavigatorContent;
import io.mattcarroll.hover.defaulthovermenu.menus.Menu;
import io.mattcarroll.hover.defaulthovermenu.menus.MenuAction;
import io.mattcarroll.hover.defaulthovermenu.menus.MenuItem;
import io.mattcarroll.hover.defaulthovermenu.menus.MenuListNavigatorContent;
/**
* Created by Stardust on 2017/3/12.
*/
public class MainMenuNavigatorContent implements NavigatorContent {
private View mView;
public MainMenuNavigatorContent(Context context) {
mView = View.inflate(context, R.layout.floating_window_main_menu, null);
ViewBinder.bind(this);
}
@ViewBinding.Click(R.id.layout_hierarchy)
private void showLayoutHierarchy() {
if (LayoutInspector.getInstance().getCapture() == null) {
Toast.makeText(mView.getContext(), R.string.text_no_accessibility_permission_to_capture, Toast.LENGTH_SHORT).show();
} else {
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_SHOW_LAYOUT_HIERARCHY));
}
}
@ViewBinding.Click(R.id.layout_bounds)
private void showLayoutBounds() {
if (LayoutInspector.getInstance().getCapture() == null) {
Toast.makeText(mView.getContext(), R.string.text_no_accessibility_permission_to_capture, Toast.LENGTH_SHORT).show();
} else {
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_SHOW_LAYOUT_BOUNDS));
}
}
@NonNull
@Override
public View getView() {
return mView;
}
@Override
public void onShown(@NonNull Navigator navigator) {
}
@Override
public void onHidden() {
}
public View findViewById(int id) {
return mView.findViewById(id);
}
}

View File

@@ -0,0 +1,40 @@
package com.stardust.scriptdroid.external.floating_window.content;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.ImageView;
import com.stardust.scriptdroid.R;
import io.mattcarroll.hover.Navigator;
import io.mattcarroll.hover.NavigatorContent;
/**
* Created by Stardust on 2017/3/12.
*/
public class RecordNavigatorContent implements NavigatorContent {
private View mView;
public RecordNavigatorContent(Context context) {
mView = new View(context);
}
@NonNull
@Override
public View getView() {
return mView;
}
@Override
public void onShown(@NonNull Navigator navigator) {
}
@Override
public void onHidden() {
}
}

View File

@@ -0,0 +1,43 @@
package com.stardust.scriptdroid.external.floating_window.content;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.ContextThemeWrapper;
import android.view.View;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.external.floating_window.view.FloatingScriptFileListView;
import io.mattcarroll.hover.Navigator;
import io.mattcarroll.hover.NavigatorContent;
/**
* Created by Stardust on 2017/3/12.
*/
public class ScritpListNavigatorContent implements NavigatorContent {
private FloatingScriptFileListView mFloatingScriptFileListView;
public ScritpListNavigatorContent(Context context) {
mFloatingScriptFileListView = new FloatingScriptFileListView(new ContextThemeWrapper(context, R.style.AppTheme));
mFloatingScriptFileListView.setScriptFileList(SharedPrefScriptFileList.getInstance());
}
@NonNull
@Override
public View getView() {
return mFloatingScriptFileListView;
}
@Override
public void onShown(@NonNull Navigator navigator) {
}
@Override
public void onHidden() {
}
}

View File

@@ -0,0 +1,75 @@
package com.stardust.scriptdroid.external.floating_window.view;
import android.content.Context;
import android.graphics.Color;
import android.view.KeyEvent;
import android.view.WindowManager;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;
import com.stardust.scriptdroid.external.floating_window.HoverMenuService;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.scriptdroid.layout_inspector.view.LayoutBoundsView;
import com.stardust.scriptdroid.layout_inspector.view.NodeInfoView;
import com.stardust.scriptdroid.layout_inspector.view.OnNodeInfoSelectListener;
import com.stardust.util.MessageEvent;
import org.greenrobot.eventbus.EventBus;
/**
* Created by Stardust on 2017/3/12.
*/
public class FloatingLayoutBoundsView extends LayoutBoundsView {
private MaterialDialog mNodeInfoDialog;
private NodeInfoView mNodeInfoView;
public FloatingLayoutBoundsView(Context context) {
super(context);
init();
}
private void init() {
setOnNodeInfoSelectListener(new OnNodeInfoSelectListener() {
@Override
public void onNodeSelect(NodeInfo info) {
showNodeInfo(info);
}
});
setBackgroundColor(0x66000000);
setVisibility(GONE);
getPaint().setColor(0xFF222222);
getPaint().setStrokeWidth(2f);
}
private void showNodeInfo(NodeInfo info) {
ensureDialog();
mNodeInfoView.setNodeInfo(info);
mNodeInfoDialog.show();
}
private void ensureDialog() {
if (mNodeInfoDialog == null) {
mNodeInfoView = new NodeInfoView(getContext());
mNodeInfoDialog = new MaterialDialog.Builder(getContext())
.customView(mNodeInfoView, false)
.theme(Theme.LIGHT)
.build();
mNodeInfoDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_SHOW_AND_EXPAND_MENU));
setVisibility(GONE);
return true;
}
return super.dispatchKeyEvent(event);
}
}

View File

@@ -0,0 +1,79 @@
package com.stardust.scriptdroid.external.floating_window.view;
import android.content.Context;
import android.content.DialogInterface;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floating_window.HoverMenuService;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.scriptdroid.layout_inspector.view.LayoutHierarchyView;
import com.stardust.scriptdroid.layout_inspector.view.NodeInfoView;
import com.stardust.scriptdroid.layout_inspector.view.OnNodeInfoSelectListener;
import com.stardust.util.MessageEvent;
import org.greenrobot.eventbus.EventBus;
/**
* Created by Stardust on 2017/3/12.
*/
public class FloatingLayoutHierarchyView extends LayoutHierarchyView {
private static final String TAG = "FloatingHierarchyView";
private MaterialDialog mNodeInfoDialog;
private NodeInfoView mNodeInfoView;
public FloatingLayoutHierarchyView(Context context) {
super(context);
init();
}
private void init() {
setBackgroundColor(0x99ffffff);
setVisibility(GONE);
setShowClickedNodeBounds(true);
getBoundsPaint().setStrokeWidth(3);
getBoundsPaint().setColor(0xFFD32F2F);
setOnNodeInfoLongClickListener(new OnNodeInfoSelectListener() {
@Override
public void onNodeSelect(NodeInfo info) {
showNodeInfo(info);
}
});
}
private void showNodeInfo(NodeInfo info) {
ensureDialog();
mNodeInfoView.setNodeInfo(info);
mNodeInfoDialog.show();
}
private void ensureDialog() {
if (mNodeInfoDialog == null) {
mNodeInfoView = new NodeInfoView(getContext());
mNodeInfoDialog = new MaterialDialog.Builder(getContext())
.customView(mNodeInfoView, false)
.theme(Theme.LIGHT)
.build();
mNodeInfoDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_SHOW_AND_EXPAND_MENU));
setVisibility(GONE);
return true;
}
return super.dispatchKeyEvent(event);
}
}

View File

@@ -0,0 +1,215 @@
package com.stardust.scriptdroid.external.floating_window.view;
import android.content.Context;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.external.floating_window.HoverMenuService;
import com.stardust.scriptdroid.tool.ViewTool;
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation;
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperationPopupMenu;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.MessageEvent;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/3/12.
*/
public class FloatingScriptFileListView extends RecyclerView {
private ScriptFileList mScriptFileList;
private final OnClickListener mOnItemClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
int position = getChildViewHolder(v).getAdapterPosition();
onItemClicked(v, position);
}
};
private final OnClickListener mOnEditIconClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
int position = getChildViewHolder((View) v.getParent()).getAdapterPosition();
onEditIconClick(v, position);
}
};
private final OnClickListener mOnMoreIconClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
mOperateFileIndex = getChildViewHolder((View) v.getParent()).getAdapterPosition();
showOrDismissOperationPopupMenu(v);
}
};
private ScriptFileOperationPopupMenu mScriptFileOperationPopupMenu;
private int mOperateFileIndex;
public FloatingScriptFileListView(Context context) {
super(context);
init();
}
private void init() {
setAdapter(new Adapter());
setLayoutManager(new LinearLayoutManager(getContext()));
addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
initScriptFileOperationPopupMenu();
}
@Subscribe
public void showMessage(ScriptFileOperation.ShowMessageEvent event) {
Toast.makeText(getContext(), event.messageResId, Toast.LENGTH_SHORT).show();
}
private void initScriptFileOperationPopupMenu() {
mScriptFileOperationPopupMenu = new ScriptFileOperationPopupMenu(getContext(), getScriptFileOperations());
mScriptFileOperationPopupMenu.setOnItemClickListener(new ScriptFileOperationPopupMenu.OnItemClickListener() {
@Override
public void onClick(View view, int position, ScriptFileOperation operation) {
operation.operate(FloatingScriptFileListView.this, mScriptFileList, mOperateFileIndex);
mScriptFileOperationPopupMenu.dismiss();
if (!(operation instanceof ScriptFileOperation.Rename))
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU));
}
});
}
protected List<ScriptFileOperation> getScriptFileOperations() {
List<ScriptFileOperation> scriptFileOperations = new ArrayList<>();
scriptFileOperations.add(new ScriptFileOperation.Run());
scriptFileOperations.add(new ScriptFileOperation.Rename() {
@Override
public void operate(final RecyclerView recyclerView, final ScriptFileList scriptFileList, final int position) {
String oldName = scriptFileList.get(position).name;
MaterialDialog dialog = new ThemeColorMaterialDialogBuilder(recyclerView.getContext())
.title(R.string.text_rename)
.checkBoxPrompt(App.getApp().getString(R.string.text_rename_file_meanwhile), false, null)
.input(App.getApp().getString(R.string.text_please_input_new_name), oldName, new MaterialDialog.InputCallback() {
@Override
public void onInput(@NonNull MaterialDialog dialog, CharSequence input) {
scriptFileList.rename(position, input.toString(), dialog.isPromptCheckBoxChecked());
recyclerView.getAdapter().notifyItemChanged(position);
}
})
.build();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
}
});
scriptFileOperations.add(new ScriptFileOperation.OpenByOtherApp());
scriptFileOperations.add(new ScriptFileOperation.CreateShortcut());
scriptFileOperations.add(new ScriptFileOperation.Remove());
scriptFileOperations.add(new ScriptFileOperation.Delete());
return scriptFileOperations;
}
protected void onItemClicked(View v, int position) {
new ScriptFileOperation.Run().operate(this, mScriptFileList, position);
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU));
}
protected void onEditIconClick(View v, int position) {
new ScriptFileOperation.Edit().operate(this, mScriptFileList, position);
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU));
}
public void setScriptFileList(ScriptFileList scriptFileList) {
mScriptFileList = scriptFileList;
getAdapter().notifyDataSetChanged();
}
private void showOrDismissOperationPopupMenu(View v) {
if (mScriptFileOperationPopupMenu.isShowing()) {
mScriptFileOperationPopupMenu.dismiss();
} else {
mScriptFileOperationPopupMenu.show(v);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
EventBus.getDefault().register(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
EventBus.getDefault().unregister(this);
}
@Subscribe
public void onMenuCollapsing(MessageEvent event) {
if (event.message.equals(HoverMenuService.MESSAGE_MENU_COLLAPSING) && mScriptFileOperationPopupMenu.isShowing()) {
mScriptFileOperationPopupMenu.dismiss();
}
}
private class Adapter extends RecyclerView.Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(getContext()).inflate(R.layout.floating_script_list_recycler_view_item, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ScriptFile scriptFile = mScriptFileList.get(position);
holder.name.setText(scriptFile.name);
holder.path.setText(trimFilePath(scriptFile.path));
}
private final String SD_CARD_PATH = Environment.getExternalStorageDirectory().toString();
private String trimFilePath(String path) {
if (path.startsWith(SD_CARD_PATH)) {
path = path.substring(SD_CARD_PATH.length());
}
return path;
}
@Override
public int getItemCount() {
return mScriptFileList.size();
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
TextView name, path;
ViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
path = (TextView) itemView.findViewById(R.id.path);
ViewTool.$(itemView, R.id.edit).setOnClickListener(mOnEditIconClickListener);
ViewTool.$(itemView, R.id.more).setOnClickListener(mOnMoreIconClickListener);
itemView.setOnClickListener(mOnItemClickListener);
}
}
}

View File

@@ -1,97 +0,0 @@
package com.stardust.scriptdroid.external.notification.bounds_assist;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
import com.stardust.util.StateObserver;
/**
* Created by Stardust on 2017/2/2.
*/
public class BoundsAssistSwitchNotification {
private static final int NOTIFY_ID = 11126;
public static final String KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE = "KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE";
private static boolean enable = false;
public static boolean isEnable() {
return enable;
}
static {
App.getStateObserver().register(KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE, new StateObserver.OnStateChangedListener() {
@Override
public void onStateChanged(boolean newState) {
setEnable(newState);
}
@Override
public void initState(boolean state) {
enable = state;
if (enable) {
showNotification();
}
}
});
App.getStateObserver().register(BoundsAssistant.KEY_BOUNDS_ASSIST_ENABLE, new StateObserver.OnStateChangedListener() {
@Override
public void onStateChanged(boolean newState) {
if (enable) {
setEnable(false);
setEnable(true);
}
}
@Override
public void initState(boolean state) {
}
});
}
public static void setEnable(boolean enable) {
if (BoundsAssistSwitchNotification.enable == enable)
return;
BoundsAssistSwitchNotification.enable = enable;
PreferenceManager.getDefaultSharedPreferences(App.getApp()).edit().putBoolean(KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE, enable).apply();
if (enable) {
showNotification();
} else {
hideNotification();
}
}
private static void showNotification() {
Notification notification = new NotificationCompat.Builder(App.getApp())
.setAutoCancel(false)
.setSmallIcon(R.drawable.ic_robot_head)
.setDeleteIntent(BoundsAssistSwitchNotificationHandleService.getDeletePendingIntent())
.setContentText(BoundsAssistant.isAssistModeEnable() ?
App.getApp().getString(R.string.text_assist_mode_enabled) :
App.getApp().getString(R.string.text_assist_mode_disabled))
.setContentIntent(BoundsAssistSwitchNotificationHandleService.getStartIntent())
.build();
showNotification(notification);
}
private static void showNotification(Notification notification) {
NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFY_ID, notification);
}
private static void hideNotification() {
NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFY_ID);
}
}

View File

@@ -1,67 +0,0 @@
package com.stardust.scriptdroid.external.notification.bounds_assist;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
import static com.stardust.scriptdroid.external.notification.bounds_assist.BoundsAssistSwitchNotification.KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE;
/**
* Created by Stardust on 2017/2/2.
*/
public class BoundsAssistSwitchNotificationHandleService extends Service {
private static final String EXTRA_INTENT_VALID = "BoundsAssistSwitchNotificationHandleService.intentValid";
private static final String EXTRA_ACTION = "action";
private static final int ACTION_TOGGLE_ASSIST_MODE = 1;
private static final int ACTION_CANCEL_ASSIST_MODE_NOTIFICATION = 2;
public static PendingIntent getStartIntent() {
Intent intent = new Intent(App.getApp(), BoundsAssistSwitchNotificationHandleService.class)
.putExtra(EXTRA_INTENT_VALID, true)
.putExtra(EXTRA_ACTION, ACTION_TOGGLE_ASSIST_MODE);
return PendingIntent.getService(App.getApp(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
boolean intentValid = intent.getBooleanExtra(EXTRA_INTENT_VALID, false);
if (intentValid) {
performAction(intent.getIntExtra(EXTRA_ACTION, 0));
}
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
private void performAction(int action) {
switch (action) {
case ACTION_TOGGLE_ASSIST_MODE:
BoundsAssistant.setAssistModeEnable(!BoundsAssistant.isAssistModeEnable());
break;
case ACTION_CANCEL_ASSIST_MODE_NOTIFICATION:
App.getStateObserver().setState(KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE, false);
break;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
public static PendingIntent getDeletePendingIntent() {
Intent deleteIntent = new Intent(App.getApp(), BoundsAssistSwitchNotificationHandleService.class)
.putExtra(EXTRA_INTENT_VALID, true)
.putExtra(EXTRA_ACTION, ACTION_CANCEL_ASSIST_MODE_NOTIFICATION);
return PendingIntent.getService(App.getApp(), 0, deleteIntent, PendingIntent.FLAG_ONE_SHOT);
}
}

View File

@@ -8,9 +8,9 @@ import android.os.IBinder;
import android.support.annotation.Nullable;
import android.widget.Toast;
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.service.VolumeChangeListenService;
import com.stardust.scriptdroid.ui.main.MainActivity;

View File

@@ -7,8 +7,8 @@ import android.text.TextUtils;
import android.widget.Toast;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.ui.edit.EditActivity;
import com.stardust.scriptdroid.R;
/**
* Created by Stardust on 2017/2/2.

View File

@@ -8,12 +8,12 @@ import android.text.TextUtils;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.ui.main.MainActivity;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.scriptdroid.R;
import java.io.File;

View File

@@ -7,8 +7,8 @@ import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.shortcut.ShortcutActivity;
import com.stardust.scriptdroid.R;
/**
* Created by Stardust on 2017/2/22.

View File

@@ -6,8 +6,8 @@ import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.R;
import java.io.File;
import java.util.LinkedList;

View File

@@ -1,32 +0,0 @@
package com.stardust.scriptdroid.external.tile;
import android.os.Build;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.support.annotation.RequiresApi;
import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
/**
* Created by Stardust on 2017/1/26.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public class BoundsAssistEnableTileService extends TileService {
public void onClick() {
BoundsAssistant.setAssistModeEnable(!BoundsAssistant.isAssistModeEnable());
updateTile();
}
private void updateTile() {
getQsTile().setState(BoundsAssistant.isAssistModeEnable() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
getQsTile().updateTile();
}
@Override
public void onStartListening() {
updateTile();
}
}

View File

@@ -2,13 +2,13 @@ package com.stardust.scriptdroid.file;
import android.content.Context;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.util.MapEntries;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import java.util.LinkedHashMap;
import java.util.Map;

View File

@@ -0,0 +1,53 @@
package com.stardust.scriptdroid.layout_inspector;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityDelegate;
/**
* Created by Stardust on 2017/3/10.
*/
public class LayoutInspector implements AccessibilityDelegate {
private static LayoutInspector instance = new LayoutInspector();
public static LayoutInspector getInstance() {
return instance;
}
private AccessibilityNodeInfo mRootInActiveWindow;
private NodeInfo mCapture;
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
mRootInActiveWindow = service.getRootInActiveWindow();
return false;
}
public AccessibilityNodeInfo getRootInActiveWindow() {
return mRootInActiveWindow;
}
public NodeInfo captureCurrentWindow() {
if (mRootInActiveWindow == null) {
mCapture = null;
} else {
mCapture = NodeInfo.capture(mRootInActiveWindow);
}
return mCapture;
}
public void clearCapture() {
mRootInActiveWindow = null;
mCapture = null;
}
public NodeInfo getCapture() {
return mCapture;
}
}

View File

@@ -0,0 +1,88 @@
package com.stardust.scriptdroid.layout_inspector;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/3/10.
*/
public class NodeInfo {
private List<NodeInfo> children = new ArrayList<>();
public String id;
public CharSequence contentDesc, className, packageName, text;
public int drawingOrder;
public boolean accessibilityFocused, checked, clickable, contextClickable, dismissable, editable, enabled,
focusable, longClickable, selected, scrollable, visibleToUser;
public String bounds;
private Rect mBoundsInScreen;
public NodeInfo(AccessibilityNodeInfoCompat node) {
id = simplifyId(node.getViewIdResourceName());
contentDesc = node.getContentDescription();
className = node.getClassName();
packageName = node.getPackageName();
text = node.getText();
drawingOrder = node.getDrawingOrder();
accessibilityFocused = node.isAccessibilityFocused();
checked = node.isChecked();
clickable = node.isClickable();
contextClickable = node.isContextClickable();
dismissable = node.isDismissable();
enabled = node.isEnabled();
editable = node.isEditable();
focusable = node.isFocusable();
longClickable = node.isLongClickable();
selected = node.isSelected();
scrollable = node.isScrollable();
visibleToUser = node.isVisibleToUser();
mBoundsInScreen = new Rect();
node.getBoundsInScreen(mBoundsInScreen);
bounds = boundsToString(mBoundsInScreen);
}
private String simplifyId(String idResourceName) {
if(idResourceName == null)
return null;
int i = idResourceName.indexOf('/');
return idResourceName.substring(i + 1);
}
public Rect getBoundsInScreen() {
return mBoundsInScreen;
}
public static String boundsToString(Rect rect) {
return rect.toString().replace('-', ',').replace(" ", "").substring(4);
}
public NodeInfo(AccessibilityNodeInfo node) {
this(new AccessibilityNodeInfoCompat(node));
}
public static NodeInfo capture(@NonNull AccessibilityNodeInfo root) {
NodeInfo nodeInfo = new NodeInfo(root);
for (int i = 0; i < root.getChildCount(); i++) {
AccessibilityNodeInfo child = root.getChild(i);
if (child != null) {
nodeInfo.children.add(capture(child));
}
}
return nodeInfo;
}
public List<NodeInfo> getChildren() {
return children;
}
}

View File

@@ -0,0 +1,121 @@
package com.stardust.scriptdroid.layout_inspector.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.scriptdroid.tool.ViewTool;
/**
* Created by Stardust on 2017/3/10.
*/
public class LayoutBoundsView extends View {
private NodeInfo mRootNode;
private Paint mPaint;
private int mStatusBarHeight;
private OnNodeInfoSelectListener mOnNodeInfoSelectListener;
public LayoutBoundsView(Context context) {
super(context);
init();
}
public LayoutBoundsView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public LayoutBoundsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public LayoutBoundsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public void setOnNodeInfoSelectListener(OnNodeInfoSelectListener onNodeInfoSelectListener) {
mOnNodeInfoSelectListener = onNodeInfoSelectListener;
}
public void setRootNode(NodeInfo rootNode) {
mRootNode = rootNode;
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.STROKE);
mStatusBarHeight = ViewTool.getStatusBarHeight(getContext());
setWillNotDraw(false);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
draw(canvas, mRootNode);
}
public Paint getPaint() {
return mPaint;
}
private void draw(Canvas canvas, NodeInfo node) {
if (node == null)
return;
drawRect(canvas, node.getBoundsInScreen(), mStatusBarHeight, mPaint);
for (NodeInfo child : node.getChildren()) {
draw(canvas, child);
}
}
static void drawRect(Canvas canvas, Rect rect, int statusBarHeight, Paint paint) {
Rect offsetRect = new Rect(rect);
offsetRect.offset(0, -statusBarHeight);
canvas.drawRect(offsetRect, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && mRootNode != null) {
NodeInfo nodeInfo = findNodeAt(mRootNode, (int) event.getRawX(), (int) event.getRawY());
onNodeInfoClick(nodeInfo);
}
return super.onTouchEvent(event);
}
private void onNodeInfoClick(NodeInfo nodeInfo) {
if (mOnNodeInfoSelectListener != null) {
mOnNodeInfoSelectListener.onNodeSelect(nodeInfo);
}
}
private NodeInfo findNodeAt(NodeInfo node, int x, int y) {
for (NodeInfo child : node.getChildren()) {
if (child != null && child.getBoundsInScreen().contains(x, y)) {
return findNodeAt(child, x, y);
}
}
return node;
}
}

View File

@@ -0,0 +1,210 @@
package com.stardust.scriptdroid.layout_inspector.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.scriptdroid.tool.ViewTool;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import pl.openrnd.multilevellistview.ItemInfo;
import pl.openrnd.multilevellistview.MultiLevelListAdapter;
import pl.openrnd.multilevellistview.MultiLevelListView;
import pl.openrnd.multilevellistview.NestType;
import pl.openrnd.multilevellistview.OnItemClickListener;
/**
* Created by Stardust on 2017/3/10.
*/
public class LayoutHierarchyView extends MultiLevelListView {
private Adapter mAdapter;
private OnNodeInfoSelectListener mOnNodeInfoSelectListener;
private AdapterView.OnItemLongClickListener mOnItemLongClickListenerProxy = new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (mOnNodeInfoSelectListener != null) {
mOnNodeInfoSelectListener.onNodeSelect(((ViewHolder) view.getTag()).nodeInfo);
return true;
}
return false;
}
};
private Paint mPaint;
private int mStatusBarHeight;
private NodeInfo mClickedNodeInfo;
private boolean mShowClickedNodeBounds;
public LayoutHierarchyView(Context context) {
super(context);
init();
}
public LayoutHierarchyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LayoutHierarchyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setShowClickedNodeBounds(boolean showClickedNodeBounds) {
mShowClickedNodeBounds = showClickedNodeBounds;
}
private void init() {
mAdapter = new Adapter();
setAdapter(mAdapter);
setNestType(NestType.MULTIPLE);
((ListView) getChildAt(0)).setOnItemLongClickListener(mOnItemLongClickListenerProxy);
setWillNotDraw(false);
initPaint();
setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClicked(MultiLevelListView parent, View view, Object item, ItemInfo itemInfo) {
mClickedNodeInfo = (NodeInfo) item;
invalidate();
}
@Override
public void onGroupItemClicked(MultiLevelListView parent, View view, Object item, ItemInfo itemInfo) {
mClickedNodeInfo = (NodeInfo) item;
invalidate();
}
});
}
private void initPaint() {
mPaint = new Paint();
mPaint.setColor(Color.DKGRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(3);
mStatusBarHeight = ViewTool.getStatusBarHeight(getContext());
}
public Paint getBoundsPaint() {
return mPaint;
}
public void setRootNode(NodeInfo rootNodeInfo) {
mAdapter.setDataItems(Collections.singletonList(rootNodeInfo));
mClickedNodeInfo = null;
}
public void setOnNodeInfoLongClickListener(final OnNodeInfoSelectListener onNodeInfoSelectListener) {
mOnNodeInfoSelectListener = onNodeInfoSelectListener;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mShowClickedNodeBounds && mClickedNodeInfo != null) {
LayoutBoundsView.drawRect(canvas, mClickedNodeInfo.getBoundsInScreen(), mStatusBarHeight, mPaint);
}
}
private class ViewHolder {
TextView nameView;
TextView infoView;
ImageView arrowView;
LevelBeamView levelBeamView;
NodeInfo nodeInfo;
ViewHolder(View view) {
infoView = (TextView) view.findViewById(R.id.dataItemInfo);
nameView = (TextView) view.findViewById(R.id.dataItemName);
arrowView = (ImageView) view.findViewById(R.id.dataItemArrow);
levelBeamView = (LevelBeamView) view.findViewById(R.id.dataItemLevelBeam);
}
}
private class Adapter extends MultiLevelListAdapter {
@Override
public List<?> getSubObjects(Object object) {
return ((NodeInfo) object).getChildren();
}
@Override
public boolean isExpandable(Object object) {
return !((NodeInfo) object).getChildren().isEmpty();
}
@Override
public View getViewForObject(Object object, View convertView, ItemInfo itemInfo) {
NodeInfo nodeInfo = (NodeInfo) object;
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.layout_hierarchy_view_item, null);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.nameView.setText(simplifyClassName(nodeInfo.className));
viewHolder.nodeInfo = nodeInfo;
if (viewHolder.infoView.getVisibility() == VISIBLE)
viewHolder.infoView.setText(getItemInfoDsc(itemInfo));
if (itemInfo.isExpandable() && !isAlwaysExpanded()) {
viewHolder.arrowView.setVisibility(View.VISIBLE);
viewHolder.arrowView.setImageResource(itemInfo.isExpanded() ?
R.drawable.arrow_up : R.drawable.arrow_down);
} else {
viewHolder.arrowView.setVisibility(View.GONE);
}
viewHolder.levelBeamView.setLevel(itemInfo.getLevel());
return convertView;
}
private String simplifyClassName(CharSequence className) {
String s = className.toString();
if (s.startsWith("android.widget.")) {
s = s.substring(15);
}
return s;
}
private String getItemInfoDsc(ItemInfo itemInfo) {
StringBuilder builder = new StringBuilder();
builder.append(String.format(Locale.getDefault(), "level[%d], idx in level[%d/%d]",
itemInfo.getLevel() + 1, /*Indexing starts from 0*/
itemInfo.getIdxInLevel() + 1 /*Indexing starts from 0*/,
itemInfo.getLevelSize()));
if (itemInfo.isExpandable()) {
builder.append(String.format(", expanded[%b]", itemInfo.isExpanded()));
}
return builder.toString();
}
}
}

View File

@@ -0,0 +1,92 @@
package com.stardust.scriptdroid.layout_inspector.view;
import android.content.Context;
import android.os.Build;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.StyleRes;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ViewSwitcher;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.layout_inspector.LayoutInspector;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
/**
* Created by Stardust on 2017/3/10.
*/
public class LayoutInspectView extends FrameLayout {
private ViewSwitcher mViewSwitcher;
private NodeInfoView mNodeInfoView;
private LayoutHierarchyView mLayoutBoundsView;
private View mCurrentView;
public LayoutInspectView(@NonNull Context context) {
super(context);
init();
}
public LayoutInspectView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public LayoutInspectView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public LayoutInspectView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
inflate(getContext(), R.layout.floating_window_expand, this);
mNodeInfoView = (NodeInfoView) findViewById(R.id.node_info);
mLayoutBoundsView = (LayoutHierarchyView) findViewById(R.id.bounds_view);
mViewSwitcher = (ViewSwitcher) findViewById(R.id.view_switcher);
mCurrentView = mNodeInfoView;
mLayoutBoundsView.setOnNodeInfoLongClickListener(new OnNodeInfoSelectListener() {
@Override
public void onNodeSelect(NodeInfo info) {
mNodeInfoView.setNodeInfo(info);
showNodeInfoView();
}
});
}
public void showNodeInfoView() {
if (mCurrentView != mNodeInfoView) {
mViewSwitcher.showPrevious();
mCurrentView = mNodeInfoView;
}
}
public void showLayoutBoundsView() {
mLayoutBoundsView.setRootNode(LayoutInspector.getInstance().captureCurrentWindow());
backToLayoutBoundsView();
}
public boolean isLayoutBoundsViewShowing() {
return mCurrentView == mLayoutBoundsView;
}
public boolean isNodeInfoViewShowing() {
return mCurrentView == mNodeInfoView;
}
public void backToLayoutBoundsView() {
if (mCurrentView != mLayoutBoundsView) {
mViewSwitcher.showNext();
mCurrentView = mLayoutBoundsView;
}
}
}

View File

@@ -0,0 +1,97 @@
package com.stardust.scriptdroid.layout_inspector.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.stardust.scriptdroid.R;
/**
* Created by Stardust on 2017/3/10.
*/
public class LevelBeamView extends View {
private static final String TAG = "LevelBeamView";
private static final int[] colors = {
0xff1abc9c,
0xff3498db,
0xffe67e22,
0xff8e44ad,
0xfff1c40f,
0xff2ecc71,
};
private int mLevel;
private int mPaddingLeft, mPaddingRight;
private int mLinesWidth;
private int mLinesOffset;
private Paint mLinePaint;
public LevelBeamView(Context context) {
super(context);
init();
}
public LevelBeamView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LevelBeamView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setLevel(int level) {
mLevel = level;
requestLayout();
}
private void init() {
setWillNotDraw(false);
mPaddingLeft = (int) getResources().getDimension(R.dimen.level_beam_view_padding_left);
mPaddingRight = (int) getResources().getDimension(R.dimen.level_beam_view_padding_right);
mLinesWidth = (int) getResources().getDimension(R.dimen.level_beam_view_line_width);
mLinesOffset = (int) getResources().getDimension(R.dimen.level_beam_view_line_offset);
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setColor(Color.RED);
mLinePaint.setStyle(Paint.Style.FILL);
mLinePaint.setStrokeWidth(mLinesWidth);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = mPaddingLeft + mPaddingRight + (mLevel + 1) * (mLinesWidth + mLinesOffset);
int height = View.MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
Log.i(TAG, "onDraw");
super.onDraw(canvas);
for (int lvl = 0; lvl <= mLevel; lvl++) {
float LINE_X = mPaddingLeft + lvl * mLinesWidth;
if (lvl >= 1) {
LINE_X += lvl * mLinesOffset;
}
mLinePaint.setColor(getColorForLevel(lvl));
canvas.drawLine(LINE_X, 0, LINE_X, canvas.getHeight(), mLinePaint);
}
}
private int getColorForLevel(int level) {
return colors[level % colors.length];
}
}

View File

@@ -0,0 +1,115 @@
package com.stardust.scriptdroid.layout_inspector.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.scriptdroid.tool.ClipboardTool;
import java.lang.reflect.Field;
import de.codecrafters.tableview.TableDataAdapter;
import de.codecrafters.tableview.TableView;
import de.codecrafters.tableview.model.TableColumnWeightModel;
import de.codecrafters.tableview.toolkit.SimpleTableDataAdapter;
import de.codecrafters.tableview.toolkit.SimpleTableHeaderAdapter;
import de.codecrafters.tableview.toolkit.TableDataRowBackgroundProviders;
/**
* Created by Stardust on 2017/3/10.
*/
public class NodeInfoView extends TableView {
private static final Field[] fields = NodeInfo.class.getFields();
private String[][] mData = new String[fields.length][2];
public NodeInfoView(Context context) {
super(context);
init();
}
public NodeInfoView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public NodeInfoView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setNodeInfo(NodeInfo nodeInfo) {
for (int i = 0; i < fields.length; i++) {
try {
Object value = fields[i].get(nodeInfo);
mData[i][1] = value == null ? "null" : value.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
getDataAdapter().notifyDataSetChanged();
}
private void init() {
initData();
setUpTableConfig();
setAdapter();
setUpStyle();
}
private void setAdapter() {
setDataAdapter(new TableDataAdapter<String[]>(getContext(), mData) {
SimpleTableDataAdapter mSimpleTableDataAdapter = new SimpleTableDataAdapter(getContext(), mData);
@Override
public View getCellView(int rowIndex, int columnIndex, ViewGroup parentView) {
final TextView textView = (TextView) mSimpleTableDataAdapter.getCellView(rowIndex, columnIndex, parentView);
textView.setSingleLine(false);
textView.setMaxLines(3);
textView.setTextColor(0xcc000000);
textView.setTextIsSelectable(true);
textView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ClipboardTool.setClip(textView.getText());
Toast.makeText(getContext(), R.string.text_already_copy_to_clip, Toast.LENGTH_SHORT).show();
return true;
}
});
return textView;
}
});
}
private void setUpTableConfig() {
setColumnCount(2);
setColumnModel(new TableColumnWeightModel(2));
}
private void initData() {
for (int i = 0; i < mData.length; i++) {
mData[i][0] = fields[i].getName();
mData[i][1] = "";
}
}
private void setUpStyle() {
int colorEvenRows = Color.WHITE;
int colorOddRows = 0xffe7e7e7;
setDataRowBackgroundProvider(TableDataRowBackgroundProviders.alternatingRowColors(colorEvenRows, colorOddRows));
getChildAt(0).setVisibility(GONE);
}
}

View File

@@ -0,0 +1,12 @@
package com.stardust.scriptdroid.layout_inspector.view;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
/**
* Created by Stardust on 2017/3/10.
*/
public interface OnNodeInfoSelectListener {
void onNodeSelect(NodeInfo info);
}

View File

@@ -3,7 +3,7 @@ package com.stardust.scriptdroid.record;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.scriptdroid.service.AccessibilityDelegate;
import com.stardust.view.accessibility.AccessibilityDelegate;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.PAUSED;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.RECORDING;

View File

@@ -7,13 +7,12 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.scriptdroid.droid.runtime.action.FilterAction;
import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.util.SparseArrayEntries;
import com.stardust.view.accessibility.AccessibilityNodeInfoHelper;
import java.util.List;
import static com.stardust.scriptdroid.bounds_assist.BoundsAssistant.boundsToString;
import static com.stardust.scriptdroid.bounds_assist.BoundsAssistant.getBoundsInScreen;
/**
* Created by Stardust on 2017/2/14.
@@ -69,7 +68,7 @@ public class ActionRecorder {
AccessibilityNodeInfo source = event.getSource();
if (source == null)
return;
String bounds = boundsToString(getBoundsInScreen(source));
String bounds = NodeInfo.boundsToString(AccessibilityNodeInfoHelper.getBoundsInScreen(source));
source.recycle();
onAccessibilityEvent(event, bounds, sb);
}
@@ -130,7 +129,7 @@ public class ActionRecorder {
private static int findInEditableList(List<AccessibilityNodeInfo> editableList, AccessibilityNodeInfo editable) {
int i = 0;
for (AccessibilityNodeInfo nodeInfo : editableList) {
if (getBoundsInScreen(nodeInfo).equals(getBoundsInScreen(editable))) {
if (AccessibilityNodeInfoHelper.getBoundsInScreen(nodeInfo).equals(AccessibilityNodeInfoHelper.getBoundsInScreen(editable))) {
return i;
}
i++;

View File

@@ -1,20 +1,12 @@
package com.stardust.scriptdroid.record.inputevent;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Pair;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.tool.Shell;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jackpal.androidterm.ShellTermSession;
import jackpal.androidterm.emulatorview.TermSession;

View File

@@ -2,7 +2,6 @@ package com.stardust.scriptdroid.record.inputevent;
import android.support.annotation.NonNull;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.util.MapEntries;
import java.util.HashMap;

View File

@@ -3,8 +3,6 @@ package com.stardust.scriptdroid.record.inputevent;
import android.support.annotation.NonNull;
import java.text.DecimalFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**

View File

@@ -1,14 +0,0 @@
package com.stardust.scriptdroid.service;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
/**
* Created by Stardust on 2017/2/14.
*/
public interface AccessibilityDelegate {
boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event);
}

View File

@@ -5,7 +5,9 @@ import android.os.Build;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.view.accessibility.AccessibilityDelegate;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import java.lang.ref.WeakReference;
@@ -79,6 +81,7 @@ public class AccessibilityWatchDogService extends AccessibilityService {
Log.v(TAG, "onAccessibilityEvent: " + event);
synchronized (mDelegates) {
for (Map.Entry<Integer, AccessibilityDelegate> entry : mDelegates.entrySet()) {
Log.v(TAG, "delegate: " + entry.getValue().getClass().getName());
if (entry.getValue().onAccessibilityEvent(this, event))
break;
}
@@ -101,7 +104,7 @@ public class AccessibilityWatchDogService extends AccessibilityService {
if (instance != null && instance.get() != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
instance.get().disableSelf();
} else {
AccessibilityServiceUtils.goToAccessibilitySetting(App.getApp());
AccessibilityServiceTool.goToAccessibilitySetting();
}
}

View File

@@ -8,9 +8,9 @@ import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchHandleService;
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
import com.stardust.scriptdroid.Pref;
import java.lang.ref.WeakReference;

View File

@@ -1,19 +1,16 @@
package com.stardust.scriptdroid.tool;
import android.accessibilityservice.AccessibilityService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.text.TextUtils;
import android.widget.Toast;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import static com.stardust.view.accessibility.AccessibilityServiceUtils.isAccessibilityServiceEnabled;
/**
* Created by Stardust on 2017/1/26.
*/
@@ -22,12 +19,37 @@ public class AccessibilityServiceTool {
public static void enableAccessibilityService() {
if (Pref.def().getBoolean(App.getApp().getString(R.string.key_enable_accessibility_service_by_root), false)) {
if (!AccessibilityServiceUtils.enableAccessibilityServiceByRootAndWaitFor(App.getApp(), AccessibilityWatchDogService.class, 3000)) {
AccessibilityServiceUtils.goToAccessibilitySetting(App.getApp());
if (!enableAccessibilityServiceByRootAndWaitFor(AccessibilityWatchDogService.class, 3000)) {
goToAccessibilitySetting();
}
} else {
AccessibilityServiceUtils.goToAccessibilitySetting(App.getApp());
goToAccessibilitySetting();
}
}
public static void goToAccessibilitySetting() {
Context context = App.getApp();
if (Pref.isFirstGoToAccessibilitySetting()) {
Toast.makeText(context, context.getString(R.string.text_please_choose) + context.getString(R.string._app_name), Toast.LENGTH_LONG).show();
}
AccessibilityServiceUtils.goToAccessibilitySetting(context);
}
public static boolean enableAccessibilityServiceByRootAndWaitFor(Class<AccessibilityWatchDogService> accessibilityService, int timeout) {
Shell.execCommand("settings put secure enabled_accessibility_services %accessibility:"
+ App.getApp().getPackageName() + "/" + accessibilityService.getName(), true);
long millis = System.currentTimeMillis();
while (true) {
if (isAccessibilityServiceEnabled(App.getApp(), accessibilityService))
return true;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (System.currentTimeMillis() - millis >= timeout) {
return false;
}
}
}
}

View File

@@ -0,0 +1,19 @@
package com.stardust.scriptdroid.tool;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import com.stardust.scriptdroid.App;
/**
* Created by Stardust on 2017/3/10.
*/
public class ClipboardTool {
public static void setClip(CharSequence text) {
((ClipboardManager) App.getApp().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("", text));
}
}

View File

@@ -3,14 +3,9 @@ package com.stardust.scriptdroid.tool;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import com.afollestad.materialdialogs.folderselector.FileChooserDialog;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.scriptdroid.R;

View File

@@ -1,27 +1,17 @@
package com.stardust.scriptdroid.tool;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.util.Log;
import com.stardust.scriptdroid.App;
import com.stericson.RootShell.RootShell;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import jackpal.androidterm.ShellTermSession;
import jackpal.androidterm.Term;
import jackpal.androidterm.emulatorview.TermSession;
import jackpal.androidterm.util.TermSettings;
/**

View File

@@ -1,7 +1,11 @@
package com.stardust.scriptdroid.tool;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.IdRes;
import android.view.View;
import android.view.Window;
/**
* Created by Stardust on 2017/1/24.
@@ -13,4 +17,13 @@ public class ViewTool {
public static <V extends View> V $(View view, @IdRes int resId) {
return (V) view.findViewById(resId);
}
public static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}

View File

@@ -4,7 +4,6 @@ import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;

View File

@@ -9,8 +9,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import com.jraska.console.Console;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.jraska.console.Console;
import com.stardust.scriptdroid.R;
/**

View File

@@ -15,6 +15,15 @@ import android.view.View;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.ui.edit.editor920.Editor920Activity;
import com.stardust.scriptdroid.ui.edit.sidemenu.EditSideMenuFragment;
import com.stardust.scriptdroid.ui.edit.sidemenu.FunctionListRecyclerView;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.SparseArrayEntries;
import com.stardust.view.ViewBinding;
import com.stardust.widget.ToolbarMenuItem;
import com.jecelyin.editor.v2.common.Command;
import com.jecelyin.editor.v2.common.SaveListener;
import com.jecelyin.editor.v2.core.widget.TextView;
@@ -22,20 +31,10 @@ import com.jecelyin.editor.v2.ui.EditorDelegate;
import com.jecelyin.editor.v2.view.EditorView;
import com.jecelyin.editor.v2.view.menu.MenuDef;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.ui.edit.completion.InputMethodEnhanceBar;
import com.stardust.scriptdroid.ui.edit.editor920.Editor920Activity;
import com.stardust.scriptdroid.ui.edit.sidemenu.AssistClipListRecyclerView;
import com.stardust.scriptdroid.ui.edit.sidemenu.EditSideMenuFragment;
import com.stardust.scriptdroid.ui.edit.sidemenu.FunctionListRecyclerView;
import com.stardust.theme.ThemeColorManager;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.SparseArrayEntries;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import com.stardust.widget.ToolbarMenuItem;
import java.io.File;
@@ -154,15 +153,7 @@ public class EditActivity extends Editor920Activity {
insertText(function.name);
mDrawerLayout.closeDrawer(GravityCompat.END);
}
})
.setOnClipClickListener(new AssistClipListRecyclerView.OnClipClickListener() {
@Override
public void onClick(String clip, int position) {
insertText(clip);
mDrawerLayout.closeDrawer(GravityCompat.END);
}
});
}
private void setUpEditor() {

View File

@@ -4,8 +4,8 @@ import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import com.jecelyin.editor.v2.core.widget.TextView;
import com.stardust.scriptdroid.Pref;
import com.jecelyin.editor.v2.core.widget.TextView;
import java.util.ArrayList;
import java.util.Collection;

View File

@@ -1,130 +0,0 @@
package com.stardust.scriptdroid.ui.edit.sidemenu;
import android.bug.WrapContentLinearLayoutManager;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.bounds_assist.BoundsAssistClipList;
import com.stardust.scriptdroid.bounds_assist.SharedPrefBoundsAssistClipList;
import com.stardust.widget.ExpandableRecyclerView;
/**
* Created by Stardust on 2017/2/4.
*/
public class AssistClipListRecyclerView extends ExpandableRecyclerView {
public interface OnClipClickListener {
void onClick(String clip, int position);
}
private BoundsAssistClipList mBoundsAssistClipList = SharedPrefBoundsAssistClipList.getInstance();
private OnClipClickListener mOnClipClickListener;
public AssistClipListRecyclerView(Context context) {
super(context);
init();
}
public AssistClipListRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public AssistClipListRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setLayoutManager(new WrapContentLinearLayoutManager(getContext()));
setAdapter(new Adapter());
setOnChildClickListener(new OnChildClickListener() {
@Override
public void onClick(View view, int position) {
if (mOnClipClickListener != null) {
String clip = (String) ((ChildViewHolder) getChildViewHolder(view)).mTextView.getText();
mOnClipClickListener.onClick(clip, position);
}
}
});
mBoundsAssistClipList.setOnClipChangedListener(new BoundsAssistClipList.OnClipChangedListener() {
@Override
public void onClipRemove(int position) {
if (isExpanded())
getAdapter().notifyItemRemoved(position);
}
@Override
public void onClipInsert(int position) {
if (isExpanded())
getAdapter().notifyItemInserted(position);
}
@Override
public void onChange() {
getAdapter().notifyDataSetChanged();
}
});
}
public void setOnClipClickListener(OnClipClickListener onClipClickListener) {
mOnClipClickListener = onClipClickListener;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
//防止因mAssistClipList持有OnClipChangedListener引用从而导致这个RecyclerView及其Context被引用内存泄漏
mBoundsAssistClipList.setOnClipChangedListener(null);
}
private class Adapter extends ExpandableRecyclerView.DefaultTitleAdapter {
Adapter() {
setIcon(R.drawable.ic_robot_head_green);
setTitle(R.string.text_assist_clip);
}
@Override
protected RecyclerView.ViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) {
return new ChildViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.assist_clip_list_recycler_view_item, parent, false));
}
@Override
protected void onBindChildViewHolder(RecyclerView.ViewHolder holder, int position) {
ChildViewHolder viewHolder = (ChildViewHolder) holder;
viewHolder.mTextView.setText(mBoundsAssistClipList.get(position));
}
@Override
protected int getChildItemCount() {
return mBoundsAssistClipList.size();
}
@Override
protected int getChildItemViewType(int position) {
return 0;
}
}
private class ChildViewHolder extends RecyclerView.ViewHolder {
TextView mTextView;
ChildViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.clip);
}
}
}

View File

@@ -9,27 +9,23 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.stardust.app.Fragment;
import com.stardust.scriptdroid.external.floating_window.FloatingWindowManger;
import com.stardust.view.ViewBinding;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.ui.help.DocumentationActivity;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.ui.console.ConsoleActivity;
import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
import com.stardust.scriptdroid.external.notification.bounds_assist.BoundsAssistSwitchNotification;
import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import static com.stardust.scriptdroid.external.notification.bounds_assist.BoundsAssistSwitchNotification.KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE;
/**
* Created by Stardust on 2017/2/4.
*/
public class EditSideMenuFragment extends com.stardust.app.Fragment {
public class EditSideMenuFragment extends Fragment {
private FunctionListRecyclerView.OnFunctionClickListener mOnFunctionClickListener;
private AssistClipListRecyclerView.OnClipClickListener mOnClipClickListener;
public static EditSideMenuFragment setFragment(AppCompatActivity activity, int viewId) {
EditSideMenuFragment fragment = new EditSideMenuFragment();
@@ -37,7 +33,7 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
return fragment;
}
private SwitchCompat mAssistServiceSwitch, mAssistServiceNotificationSwitch;
private SwitchCompat mFloatingWindowSwitch;
@Nullable
@Override
@@ -48,7 +44,7 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
@Override
public void onStart() {
super.onStart();
if (mAssistServiceSwitch == null) {
if (mFloatingWindowSwitch == null) {
setUpUI();
}
syncSwitchState();
@@ -57,14 +53,9 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
private void setUpUI() {
setUpSwitchCompat();
setUpFunctionList();
setUpClipList();
ViewBinder.bind(this);
}
private void setUpClipList() {
AssistClipListRecyclerView assistClipListRecyclerView = $(R.id.assist_clip_list);
assistClipListRecyclerView.setOnClipClickListener(mOnClipClickListener);
}
private void setUpFunctionList() {
FunctionListRecyclerView functionListRecyclerView = $(R.id.function_list);
@@ -73,15 +64,11 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
}
private void syncSwitchState() {
mAssistServiceSwitch.setChecked(BoundsAssistant.isAssistModeEnable());
mAssistServiceNotificationSwitch.setChecked(BoundsAssistSwitchNotification.isEnable());
mFloatingWindowSwitch.setChecked(FloatingWindowManger.isFloatingWindowShowing());
}
private void setUpSwitchCompat() {
mAssistServiceSwitch = $(R.id.sw_assist_service);
mAssistServiceNotificationSwitch = $(R.id.sw_assist_service_notification);
App.getStateObserver().register(BoundsAssistant.KEY_BOUNDS_ASSIST_ENABLE, mAssistServiceSwitch);
App.getStateObserver().register(KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE, mAssistServiceNotificationSwitch);
mFloatingWindowSwitch = $(R.id.sw_floating_window);
}
@ViewBinding.Click(R.id.syntax_and_api)
@@ -94,24 +81,18 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
startActivity(new Intent(getContext(), ConsoleActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
@ViewBinding.Check(R.id.sw_assist_service)
private void setAssistServiceEnable(boolean enable) {
BoundsAssistant.setAssistModeEnable(enable);
@ViewBinding.Check(R.id.sw_floating_window)
private void setFloatingWindowEnable(boolean enable) {
if (enable && !FloatingWindowManger.isFloatingWindowShowing()) {
FloatingWindowManger.showFloatingWindow();
} else if (!enable && FloatingWindowManger.isFloatingWindowShowing()) {
FloatingWindowManger.hideFloatingWindow();
}
}
@ViewBinding.Click(R.id.assist_service)
@ViewBinding.Click(R.id.floating_window)
private void toggleAssistServiceSwitch() {
mAssistServiceSwitch.toggle();
}
@ViewBinding.Check(R.id.sw_assist_service_notification)
private void setAssistServiceNotificationEnable(boolean enable) {
BoundsAssistSwitchNotification.setEnable(enable);
}
@ViewBinding.Click(R.id.assist_service_notification)
private void toggleAssistServiceNotificationSwitch() {
mAssistServiceNotificationSwitch.toggle();
mFloatingWindowSwitch.toggle();
}
public EditSideMenuFragment setOnFunctionClickListener(FunctionListRecyclerView.OnFunctionClickListener onFunctionClickListener) {
@@ -119,8 +100,4 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
return this;
}
public EditSideMenuFragment setOnClipClickListener(AssistClipListRecyclerView.OnClipClickListener onClipClickListener) {
mOnClipClickListener = onClipClickListener;
return this;
}
}

View File

@@ -14,8 +14,8 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.R;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.scriptdroid.R;
import java.util.Timer;
import java.util.TimerTask;

View File

@@ -11,7 +11,6 @@ import android.widget.Toast;
import com.heinrichreimersoftware.androidissuereporter.IssueReporterActivity;
import com.heinrichreimersoftware.androidissuereporter.model.github.GithubTarget;
import com.stardust.scriptdroid.R;
import com.stardust.theme.ThemeColorManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

View File

@@ -3,13 +3,11 @@ package com.stardust.scriptdroid.ui.help;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.View;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.widget.CommonMarkdownView;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.file.FileUtils;
import com.stardust.widget.CommonMarkdownView;
import java.io.IOException;

View File

@@ -12,12 +12,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.file.FileUtils;
import com.stardust.scriptdroid.ui.BaseActivity;
import org.json.JSONObject;

View File

@@ -12,7 +12,6 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.DrawerLayout;
@@ -29,37 +28,31 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.folderselector.FileChooserDialog;
import com.stardust.app.NotRemindAgainDialog;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.BuildConfig;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchNotification;
import com.stardust.scriptdroid.file.FileUtils;
import com.stardust.scriptdroid.file.SampleFileManager;
import com.stardust.scriptdroid.record.inputevent.InputEventRecorder;
import com.stardust.scriptdroid.record.inputevent.InputEventToJsConverter;
import com.stardust.scriptdroid.record.inputevent.InputEventToJsRecorder;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.tool.BackPressedHandler;
import com.stardust.scriptdroid.tool.ImageSelector;
import com.stardust.scriptdroid.tool.Shell;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation;
import com.stardust.scriptdroid.ui.settings.SettingsActivity;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import com.stardust.widget.SlidingUpPanel;
import com.stardust.scriptdroid.BuildConfig;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.file.FileUtils;
import com.stardust.scriptdroid.tool.BackPressedHandler;
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation;
import com.stardust.view.ViewBinder;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends BaseActivity implements FileChooserDialog.FileCallback {
@@ -85,7 +78,6 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
checkPermissions();
registerReceivers();
handleIntent(getIntent());
//Shell.test(this);
}
private void registerReceivers() {

View File

@@ -3,6 +3,7 @@ package com.stardust.scriptdroid.ui.main;
import android.content.Context;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -14,17 +15,20 @@ import android.view.ViewGroup;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.tool.ViewTool;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation;
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperationPopupMenu;
import com.stardust.scriptdroid.tool.ClassTool;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import static com.stardust.scriptdroid.tool.ViewTool.$;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import static com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/1/23.
@@ -38,16 +42,15 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView {
@Override
public void onClick(View v) {
int position = getChildViewHolder(v).getAdapterPosition();
new ScriptFileOperation.Run().operate(ScriptListRecyclerView.this, mScriptFileList, position);
onItemClicked(v, position);
}
};
private final OnClickListener mOnEditIconClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
int position = getChildViewHolder((View) v.getParent()).getAdapterPosition();
new ScriptFileOperation.Edit().operate(ScriptListRecyclerView.this, mScriptFileList, position);
onEditIconClick(v, position);
}
};
@@ -55,7 +58,6 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView {
@Override
public void onClick(View v) {
mOperateFileIndex = getChildViewHolder((View) v.getParent()).getAdapterPosition();
//showOperationDialog(position);
showOrDismissOperationPopupMenu(v);
}
@@ -93,31 +95,40 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView {
}
private void initScriptFileOperationPopupMenu() {
mScriptFileOperationPopupMenu = new ScriptFileOperationPopupMenu(getContext());
mScriptFileOperationPopupMenu = new ScriptFileOperationPopupMenu(getContext(), getScriptFileOperations());
mScriptFileOperationPopupMenu.setOnItemClickListener(new ScriptFileOperationPopupMenu.OnItemClickListener() {
@Override
public void onClick(View view, int position) {
ScriptFileOperation.getOperation(position).operate(ScriptListRecyclerView.this, mScriptFileList, mOperateFileIndex);
public void onClick(View view, int position, ScriptFileOperation operation) {
operation.operate(ScriptListRecyclerView.this, mScriptFileList, mOperateFileIndex);
mScriptFileOperationPopupMenu.dismiss();
}
});
}
protected List<ScriptFileOperation> getScriptFileOperations() {
List<ScriptFileOperation> scriptFileOperations = new ArrayList<>();
scriptFileOperations.add(new ScriptFileOperation.Run());
scriptFileOperations.add(new ScriptFileOperation.Rename());
scriptFileOperations.add(new ScriptFileOperation.OpenByOtherApp());
scriptFileOperations.add(new ScriptFileOperation.CreateShortcut());
scriptFileOperations.add(new ScriptFileOperation.Remove());
scriptFileOperations.add(new ScriptFileOperation.Delete());
return scriptFileOperations;
}
protected void onItemClicked(View v, int position) {
new ScriptFileOperation.Run().operate(ScriptListRecyclerView.this, mScriptFileList, position);
}
protected void onEditIconClick(View v, int position) {
new ScriptFileOperation.Edit().operate(ScriptListRecyclerView.this, mScriptFileList, position);
}
public void setScriptFileList(ScriptFileList scriptFileList) {
mScriptFileList = scriptFileList;
getAdapter().notifyDataSetChanged();
}
private void showOperationDialog(final int position) {
new ThemeColorMaterialDialogBuilder(getContext()).items(ScriptFileOperation.getOperationNames())
.itemsCallback(new MaterialDialog.ListCallback() {
@Override
public void onSelection(MaterialDialog dialog, View itemView, int operation, CharSequence text) {
ScriptFileOperation.getOperation(operation).operate(ScriptListRecyclerView.this, mScriptFileList, position);
}
}).show();
}
private void showOrDismissOperationPopupMenu(View v) {
if (mScriptFileOperationPopupMenu.isShowing()) {
mScriptFileOperationPopupMenu.dismiss();
@@ -126,6 +137,23 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView {
}
}
@Subscribe
public void showMessage(ScriptFileOperation.ShowMessageEvent event) {
Snackbar.make(this, event.messageResId, Snackbar.LENGTH_SHORT).show();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
EventBus.getDefault().register(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
EventBus.getDefault().unregister(this);
}
private class Adapter extends RecyclerView.Adapter<ViewHolder> {
@Override
@@ -164,18 +192,10 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
path = (TextView) itemView.findViewById(R.id.path);
$(itemView, R.id.edit).setOnClickListener(mOnEditIconClickListener);
$(itemView, R.id.more).setOnClickListener(mOnMoreIconClickListener);
ViewTool.$(itemView, R.id.edit).setOnClickListener(mOnEditIconClickListener);
ViewTool.$(itemView, R.id.more).setOnClickListener(mOnMoreIconClickListener);
itemView.setOnClickListener(mOnItemClickListener);
}
}
static {
loadScriptFileOperations();
}
private static void loadScriptFileOperations() {
ClassTool.loadClasses(Run.class, Rename.class, OpenByOtherApp.class, CreateShortcut.class, Remove.class, Delete.class);
}
}

View File

@@ -13,18 +13,15 @@ import android.view.ViewGroup;
import com.stardust.app.Fragment;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.external.notification.bounds_assist.BoundsAssistSwitchNotification;
import com.stardust.scriptdroid.external.floating_window.FloatingWindowManger;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.ui.console.ConsoleActivity;
import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import static com.stardust.scriptdroid.external.notification.bounds_assist.BoundsAssistSwitchNotification.KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE;
/**
* Created by Stardust on 2017/1/30.
@@ -38,7 +35,7 @@ public class SlideMenuFragment extends Fragment {
activity.getSupportFragmentManager().beginTransaction().replace(viewId, fragment).commit();
}
private SwitchCompat mAutoOperateServiceSwitch, mAssistServiceSwitch, mAssistServiceNotificationSwitch;
private SwitchCompat mAutoOperateServiceSwitch, mAssistServiceSwitch;
@Nullable
@Override
@@ -64,16 +61,12 @@ public class SlideMenuFragment extends Fragment {
mAutoOperateServiceSwitch.setChecked(AccessibilityWatchDogService.isEnable());
}
}, 450);
mAssistServiceSwitch.setChecked(BoundsAssistant.isAssistModeEnable());
mAssistServiceNotificationSwitch.setChecked(BoundsAssistSwitchNotification.isEnable());
mAssistServiceSwitch.setChecked(FloatingWindowManger.isFloatingWindowShowing());
}
private void setUpSwitchCompat() {
mAutoOperateServiceSwitch = $(R.id.sw_auto_operate_service);
mAssistServiceSwitch = $(R.id.sw_assist_service);
mAssistServiceNotificationSwitch = $(R.id.sw_assist_service_notification);
App.getStateObserver().register(BoundsAssistant.KEY_BOUNDS_ASSIST_ENABLE, mAssistServiceSwitch);
App.getStateObserver().register(KEY_BOUNDS_ASSIST_SWITCH_NOTIFICATION_ENABLE, mAssistServiceNotificationSwitch);
mAssistServiceSwitch = $(R.id.sw_floating_window);
}
@@ -101,25 +94,20 @@ public class SlideMenuFragment extends Fragment {
}
}
@ViewBinding.Check(R.id.sw_assist_service)
private void setAssistServiceEnable(boolean enable) {
BoundsAssistant.setAssistModeEnable(enable);
@ViewBinding.Check(R.id.sw_floating_window)
private void setFloatingWindowEnable(boolean enable) {
if (enable && !FloatingWindowManger.isFloatingWindowShowing()) {
FloatingWindowManger.showFloatingWindow();
} else if (!enable && FloatingWindowManger.isFloatingWindowShowing()) {
FloatingWindowManger.hideFloatingWindow();
}
}
@ViewBinding.Click(R.id.assist_service)
@ViewBinding.Click(R.id.floating_window)
private void toggleAssistServiceSwitch() {
mAssistServiceSwitch.toggle();
}
@ViewBinding.Check(R.id.sw_assist_service_notification)
private void setAssistServiceNotificationEnable(boolean enable) {
BoundsAssistSwitchNotification.setEnable(enable);
}
@ViewBinding.Click(R.id.assist_service_notification)
private void toggleAssistServiceNotificationSwitch() {
mAssistServiceNotificationSwitch.toggle();
}
@ViewBinding.Click(R.id.stop_all_running_scripts)
private void stopAllRunningScripts() {

View File

@@ -5,17 +5,20 @@ import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.RecyclerView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.ui.edit.EditActivity;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.shortcut.ShortcutActivity;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.external.shortcut.Shortcut;
import com.stardust.scriptdroid.external.shortcut.ShortcutActivity;
import com.stardust.scriptdroid.ui.edit.EditActivity;
import com.stardust.scriptdroid.ui.main.ScriptListRecyclerView;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.List;
@@ -26,27 +29,23 @@ import java.util.List;
public abstract class ScriptFileOperation {
private static List<String> operationNames = new ArrayList<>();
private static List<ScriptFileOperation> operations = new ArrayList<>();
public static class ShowMessageEvent {
public int messageResId;
public static ScriptFileOperation getOperation(int index) {
return operations.get(index);
public ShowMessageEvent(int message) {
this.messageResId = message;
}
}
public static List<String> getOperationNames() {
return operationNames;
}
public abstract void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position);
private static void addOperation(String name, int iconResId, ScriptFileOperation operation) {
operation.mName = name;
operation.mIconResId = iconResId;
operationNames.add(name);
operations.add(operation);
}
public abstract void operate(RecyclerView recyclerView, ScriptFileList scriptFileList, int position);
private String mName;
public ScriptFileOperation(String name, int iconResId) {
mName = name;
mIconResId = iconResId;
}
private int mIconResId;
public String getName() {
@@ -59,13 +58,13 @@ public abstract class ScriptFileOperation {
public static class Run extends ScriptFileOperation {
static {
addOperation(App.getApp().getString(R.string.text_run), R.drawable.ic_play_green, new Run());
public Run() {
super(App.getApp().getString(R.string.text_run), R.drawable.ic_play_green);
}
@Override
public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
Snackbar.make(recyclerView, R.string.text_start_running, Snackbar.LENGTH_SHORT).show();
public void operate(RecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
EventBus.getDefault().post(new ShowMessageEvent(R.string.text_start_running));
ScriptFile scriptFile = scriptFileList.get(position);
scriptFile.run();
}
@@ -73,12 +72,12 @@ public abstract class ScriptFileOperation {
public static class Edit extends ScriptFileOperation {
static {
//addOperation(App.getApp().getString(R.string.text_edit)", R.drawable.ic_edit_green_48dp, new Edit());
public Edit() {
super(App.getApp().getString(R.string.text_edit), R.drawable.ic_edit_green_48dp);
}
@Override
public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
public void operate(RecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
Context context = recyclerView.getContext();
ScriptFile scriptFile = scriptFileList.get(position);
EditActivity.editFile(context, scriptFile.name, scriptFile.path);
@@ -87,12 +86,12 @@ public abstract class ScriptFileOperation {
public static class OpenByOtherApp extends ScriptFileOperation {
static {
addOperation(App.getApp().getString(R.string.text_open_by_other_apps), R.drawable.ic_open_in_new_green_48dp, new OpenByOtherApp());
public OpenByOtherApp() {
super(App.getApp().getString(R.string.text_open_by_other_apps), R.drawable.ic_open_in_new_green_48dp);
}
@Override
public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
public void operate(RecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
Context context = recyclerView.getContext();
ScriptFile scriptFile = scriptFileList.get(position);
Uri uri = Uri.parse("file://" + scriptFile.path);
@@ -102,12 +101,12 @@ public abstract class ScriptFileOperation {
public static class Rename extends ScriptFileOperation {
static {
addOperation(App.getApp().getString(R.string.text_rename), R.drawable.ic_rename_green, new Rename());
public Rename() {
super(App.getApp().getString(R.string.text_rename), R.drawable.ic_rename_green);
}
@Override
public void operate(final ScriptListRecyclerView recyclerView, final ScriptFileList scriptFileList, final int position) {
public void operate(final RecyclerView recyclerView, final ScriptFileList scriptFileList, final int position) {
String oldName = scriptFileList.get(position).name;
new ThemeColorMaterialDialogBuilder(recyclerView.getContext())
.title(R.string.text_rename)
@@ -125,13 +124,12 @@ public abstract class ScriptFileOperation {
public static class CreateShortcut extends ScriptFileOperation {
static {
addOperation(App.getApp().getString(R.string.text_send_shortcut), R.drawable.ic_shortcut_green, new CreateShortcut());
public CreateShortcut() {
super(App.getApp().getString(R.string.text_send_shortcut), R.drawable.ic_shortcut_green);
}
@Override
public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
public void operate(RecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
Context context = recyclerView.getContext();
ScriptFile scriptFile = scriptFileList.get(position);
new Shortcut(context).name(scriptFile.name)
@@ -139,18 +137,18 @@ public abstract class ScriptFileOperation {
.icon(R.drawable.ic_robot_green)
.extras(new Intent().putExtra("path", scriptFile.path))
.send();
Snackbar.make(recyclerView, R.string.text_already_create, Snackbar.LENGTH_SHORT).show();
EventBus.getDefault().post(R.string.text_already_create);
}
}
public static class Remove extends ScriptFileOperation {
static {
addOperation(App.getApp().getString(R.string.text_delete), R.drawable.ic_delete_green_48dp, new Remove());
public Remove() {
super(App.getApp().getString(R.string.text_delete), R.drawable.ic_delete_green_48dp);
}
@Override
public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
public void operate(RecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
scriptFileList.remove(position);
recyclerView.getAdapter().notifyItemRemoved(position);
}
@@ -158,14 +156,14 @@ public abstract class ScriptFileOperation {
public static class Delete extends ScriptFileOperation {
static {
addOperation(App.getApp().getString(R.string.text_delete_absolutly), R.drawable.ic_delete_forever_green_48dp, new Delete());
public Delete() {
super(App.getApp().getString(R.string.text_delete_absolutly), R.drawable.ic_delete_forever_green_48dp);
}
@Override
public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
public void operate(RecyclerView recyclerView, ScriptFileList scriptFileList, int position) {
boolean succeed = scriptFileList.deleteFromFileSystem(position);
Snackbar.make(recyclerView, succeed ? R.string.text_already_delete : R.string.text_delete_failed, Snackbar.LENGTH_SHORT).show();
EventBus.getDefault().post(new ShowMessageEvent(succeed ? R.string.text_already_delete : R.string.text_delete_failed));
recyclerView.getAdapter().notifyItemRemoved(position);
}
}

View File

@@ -18,9 +18,10 @@ import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
import com.stardust.scriptdroid.tool.ViewTool;
import com.stardust.scriptdroid.R;
import static com.stardust.scriptdroid.tool.ViewTool.$;
import java.util.List;
/**
* Created by Stardust on 2017/1/24.
@@ -30,26 +31,31 @@ public class ScriptFileOperationPopupMenu extends PopupWindow {
public interface OnItemClickListener {
void onClick(View view, int position);
void onClick(View view, int position, ScriptFileOperation operation);
}
private Context mContext;
private ScriptFileOperationListRecyclerView mOperationListRecyclerView;
private OnItemClickListener mOnItemClickListener;
private List<ScriptFileOperation> mScriptFileOperationList;
private final View.OnClickListener mOnItemClickRealListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
int position = mOperationListRecyclerView.getChildViewHolder(v).getAdapterPosition();
mOnItemClickListener.onClick(v, position);
mOnItemClickListener.onClick(v, position, mScriptFileOperationList.get(position));
}
}
};
public ScriptFileOperationPopupMenu(Context context) {
super();
public ScriptFileOperationPopupMenu(Context context, List<ScriptFileOperation> scriptFileOperationList) {
super(context);
mContext = context;
mScriptFileOperationList = scriptFileOperationList;
init();
}
@@ -77,7 +83,7 @@ public class ScriptFileOperationPopupMenu extends PopupWindow {
private float getScreenHeight() {
DisplayMetrics displaymetrics = new DisplayMetrics();
((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(displaymetrics);
return displaymetrics.heightPixels;
}
@@ -93,8 +99,9 @@ public class ScriptFileOperationPopupMenu extends PopupWindow {
private void initContentView() {
View contentView = View.inflate(mContext, R.layout.script_file_operation_popup_menu_content, null);
setContentView(contentView);
mOperationListRecyclerView = $(contentView, R.id.operation_list);
mOperationListRecyclerView = ViewTool.$(contentView, R.id.operation_list);
mOperationListRecyclerView.setOnItemClickListener(mOnItemClickRealListener);
mOperationListRecyclerView.setScriptFileOperationList(mScriptFileOperationList);
}
@@ -102,6 +109,8 @@ public class ScriptFileOperationPopupMenu extends PopupWindow {
private OnClickListener mOnItemClickListener;
private List<ScriptFileOperation> mScriptFileOperationList;
public ScriptFileOperationListRecyclerView(Context context) {
super(context);
init();
@@ -117,6 +126,10 @@ public class ScriptFileOperationPopupMenu extends PopupWindow {
init();
}
public void setScriptFileOperationList(List<ScriptFileOperation> scriptFileOperationList) {
mScriptFileOperationList = scriptFileOperationList;
}
private void init() {
setLayoutManager(new LinearLayoutManager(getContext()));
setAdapter(new Adapter<ViewHolder>() {
@@ -128,14 +141,14 @@ public class ScriptFileOperationPopupMenu extends PopupWindow {
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ScriptFileOperation operation = ScriptFileOperation.getOperation(position);
ScriptFileOperation operation = mScriptFileOperationList.get(position);
holder.operationName.setText(operation.getName());
holder.icon.setImageResource(operation.getIconResId());
}
@Override
public int getItemCount() {
return ScriptFileOperation.getOperationNames().size();
return mScriptFileOperationList.size();
}
});
}
@@ -152,8 +165,8 @@ public class ScriptFileOperationPopupMenu extends PopupWindow {
ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(mOnItemClickListener);
operationName = $(itemView, R.id.name);
icon = $(itemView, R.id.icon);
operationName = ViewTool.$(itemView, R.id.name);
icon = ViewTool.$(itemView, R.id.icon);
}
}
}

View File

@@ -6,20 +6,19 @@ import android.content.ClipboardManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.tool.IntentTool;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.view.ViewBinding;
import com.stardust.scriptdroid.BuildConfig;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.tool.IntentTool;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import moe.feng.alipay.zerosdk.AlipayZeroSdk;

View File

@@ -10,15 +10,15 @@ import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.file.SampleFileManager;
import com.stardust.scriptdroid.tool.IntentTool;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.ui.error.IssueReportActivity;
import com.stardust.util.MapEntries;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.ui.main.MainActivity;
import com.stardust.theme.app.ColorSelectActivity;
import com.stardust.theme.util.ListBuilder;
import com.stardust.util.MapEntries;
import java.util.List;
import java.util.Map;
@@ -26,7 +26,6 @@ import java.util.Map;
import de.psdev.licensesdialog.LicenseResolver;
import de.psdev.licensesdialog.LicensesDialog;
import de.psdev.licensesdialog.licenses.License;
import de.psdev.licensesdialog.licenses.MozillaPublicLicense11;
/**
* Created by Stardust on 2017/2/2.

View File

@@ -0,0 +1,20 @@
package com.stardust.theme;
import com.stardust.scriptdroid.*;
import com.stardust.scriptdroid.R;
/**
* Created by Stardust on 2017/3/12.
*/
public class ThemeColorManagerCompat {
public static int getColorPrimary() {
int color = ThemeColorManager.getColorPrimary();
if (color == 0) {
return App.getApp().getResources().getColor(R.color.colorPrimary);
} else {
return color;
}
}
}

View File

@@ -0,0 +1,43 @@
package com.stardust.util;
import android.Manifest;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.widget.Toast;
/**
* Created by Stardust on 2017/3/10.
*/
public class FloatingWindowUtils {
public static boolean checkFloatingWindowPermission(Context context, int messageResId) {
if (!isFloatingWindowPermitted(context)) {
Toast.makeText(context, messageResId, Toast.LENGTH_SHORT).show();
goToAppSetting(context);
return false;
}
return true;
}
private static void goToAppSetting(Context context) {
try {
Intent i = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
i.addCategory(Intent.CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + context.getPackageName()));
context.startActivity(i);
} catch (ActivityNotFoundException ignored) {
}
}
public static boolean isFloatingWindowPermitted(Context context) {
PackageManager pm = context.getPackageManager();
return pm.checkPermission(Manifest.permission.SYSTEM_ALERT_WINDOW, context.getPackageName())
== PackageManager.PERMISSION_GRANTED;
}
}

View File

@@ -0,0 +1,24 @@
package com.stardust.util;
import java.lang.reflect.Method;
/**
* Created by Stardust on 2017/3/12.
*/
public class MessageEvent {
public String message;
public Object param;
public MessageEvent(String message, Object param) {
this.message = message;
this.param = param;
}
public MessageEvent(String message) {
this.message = message;
}
}

View File

@@ -1,22 +0,0 @@
package com.stardust.util;
import android.util.SparseArray;
/**
* Created by Stardust on 2017/1/26.
*/
public class SparseArrayEntries<E> {
private SparseArray<E> mSparseArray = new SparseArray<>();
public SparseArrayEntries<E> entry(int key, E value) {
mSparseArray.put(key, value);
return this;
}
public SparseArray<E> sparseArray() {
return mSparseArray;
}
}

View File

@@ -0,0 +1,442 @@
package com.stardust.view;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
/**
* Created by ericbhatti on 11/24/15.
* <p>
* Modified by Stardust on 2017/3/10
*
* @author Eric Bhatti
* @since 24 November, 2015
*/
public class Floaty {
public interface FloatyOrientationListener {
/**
* This method is called before the orientation change happens, you can use this to save the data of your views so you can later populate the data back in {@link #afterOrientationChange}
*
* @param floaty The floating window
*/
public void beforeOrientationChange(Floaty floaty);
/**
* This method is called after the orientation change happens, you can use this to restore the data of your views that you saved in {@link #beforeOrientationChange}
*
* @param floaty The floating window
*/
public void afterOrientationChange(Floaty floaty);
}
public interface OnBackPressedListener {
boolean onBackPressed();
}
private View.OnClickListener mOnHeadClickListener;
private OnBackPressedListener mOnBackPressedListener;
private final View head;
private final View body;
private final Context context;
private final Notification notification;
private final int notificationId;
private static Floaty floaty;
private final FloatyOrientationListener floatyOrientationListener;
private float ratioY = 0;
private float oldWidth = 0;
private float oldX = 0;
private boolean confChange = false;
private static final String LOG_TAG = "Floaty";
/**
* @return The body of the floaty which is assigned through the {@link #createInstance} method.
*/
public View getBody() {
return floaty.body;
}
/**
* @return The head of the floaty which is assigned through the {@link #createInstance} method.
*/
public View getHead() {
return floaty.head;
}
/**
* Creates a Singleton of the Floating Window
*
* @param context The application context
* @param head The head View, upon clicking it the body is to be opened
* @param body The body View
* @param notificationId The notificationId for your notification
* @param notification The notification which is displayed for the foreground service
* @param floatyOrientationListener The {@link FloatyOrientationListener} interface with callbacks which are called when orientation changes.
* @return A Floating Window
*/
public static synchronized Floaty createInstance(Context context, View head, View body, int notificationId, Notification notification, FloatyOrientationListener
floatyOrientationListener) {
if (floaty == null) {
floaty = new Floaty(context, head, body, notificationId, notification, floatyOrientationListener);
}
return floaty;
}
/**
* Creates a Singleton of the Floating Window
*
* @param context The application context
* @param head The head View, upon clicking it the body is to be opened
* @param body The body View
* @param notificationId The notificationId for your notification
* @param notification The notification which is displayed for the foreground service
* @return A Floating Window
*/
public static synchronized Floaty createInstance(Context context, View head, View body, int notificationId, Notification notification) {
if (floaty == null) {
floaty = new Floaty(context, head, body, notificationId, notification, new FloatyOrientationListener() {
@Override
public void beforeOrientationChange(Floaty floaty) {
Log.d(LOG_TAG, "beforeOrientationChange");
}
@Override
public void afterOrientationChange(Floaty floaty) {
Log.d(LOG_TAG, "afterOrientationChange");
}
});
}
return floaty;
}
public static synchronized Floaty createInstance(Context context, View head, View body) {
return createInstance(context, head, body, -1, null);
}
/**
* @return The same instance of Floating Window, which has been created through {@link #createInstance}. Don't call this method before createInstance
*/
public static synchronized Floaty getInstance() {
if (floaty == null) {
throw new NullPointerException("Floaty not initialized! First call createInstance method, then to access Floaty in any other class call getInstance()");
}
return floaty;
}
private Floaty(Context context, View head, View body, int notificationId, Notification notification, FloatyOrientationListener floatyOrientationListener) {
this.head = head;
this.body = body;
this.context = context;
this.notification = notification;
this.notificationId = notificationId;
this.floatyOrientationListener = floatyOrientationListener;
}
/**
* Starts the service and adds it to the screen
*/
public void startService() {
Intent intent = new Intent(context, Floaty.FloatHeadService.class);
context.startService(intent);
}
/**
* Stops the service and removes it from the screen
*/
public void stopService() {
Intent intent = new Intent(context, Floaty.FloatHeadService.class);
context.stopService(intent);
}
public void setOnHeadClickListener(View.OnClickListener onHeadClickListener) {
mOnHeadClickListener = onHeadClickListener;
}
public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
mOnBackPressedListener = onBackPressedListener;
}
/**
* Helper method for notification creation.
*
* @param context
* @param contentTitle
* @param contentText
* @param notificationIcon
* @param contentIntent
* @return Notification for the Service
*/
public static Notification createNotification(Context context, String contentTitle, String contentText, int notificationIcon, PendingIntent contentIntent) {
return new NotificationCompat.Builder(context)
.setContentTitle(contentTitle)
.setContentText(contentText)
.setSmallIcon(notificationIcon)
.setContentIntent(contentIntent).build();
}
public static class FloatHeadService extends Service {
private WindowManager windowManager;
private WindowManager.LayoutParams params;
private LinearLayout mLinearLayout;
GestureDetectorCompat gestureDetectorCompat;
DisplayMetrics metrics;
private boolean didFling;
private int[] clickLocation = new int[2];
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE || newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
int[] location = new int[2];
mLinearLayout.getLocationOnScreen(location);
floaty.oldWidth = metrics.widthPixels;
floaty.confChange = true;
if (floaty.getBody().getVisibility() == View.VISIBLE) {
floaty.oldX = clickLocation[0];
floaty.ratioY = (float) (clickLocation[1]) / (float) metrics.heightPixels;
} else {
floaty.oldX = location[0];
floaty.ratioY = (float) (location[1]) / (float) metrics.heightPixels;
}
floaty.floatyOrientationListener.beforeOrientationChange(floaty);
floaty.stopService();
floaty.startService();
floaty.floatyOrientationListener.afterOrientationChange(floaty);
}
}
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(LOG_TAG, "onStartCommand");
metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
if (floaty.notification != null)
startForeground(floaty.notificationId, floaty.notification);
return START_STICKY;
}
private void showHead() {
floaty.head.setVisibility(View.VISIBLE);
floaty.body.setVisibility(View.GONE);
params.x = clickLocation[0];
params.y = clickLocation[1] - 36;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
mLinearLayout.setBackgroundColor(Color.argb(0, 0, 0, 0));
windowManager.updateViewLayout(mLinearLayout, params);
}
@Override
public void onCreate() {
super.onCreate();
Log.d(LOG_TAG, "onCreate");
mLinearLayout = new LinearLayout(getApplicationContext()) {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (floaty.mOnBackPressedListener != null && !floaty.mOnBackPressedListener.onBackPressed()) {
showHead();
return true;
}
}
if (event.getKeyCode() == KeyEvent.KEYCODE_HOME) {
showHead();
return true;
}
return super.dispatchKeyEvent(event);
}
};
gestureDetectorCompat = new GestureDetectorCompat(floaty.context, new GestureDetector.SimpleOnGestureListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
@Override
public boolean onDown(MotionEvent event) {
Log.d(LOG_TAG, "onDown");
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
didFling = false;
return false;
}
@Override
public void onShowPress(MotionEvent e) {
Log.d(LOG_TAG, "onShowPress");
floaty.head.setAlpha(0.8f);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (floaty.body.getVisibility() == View.VISIBLE) {
floaty.body.setVisibility(View.GONE);
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
mLinearLayout.setBackgroundColor(Color.argb(0, 0, 0, 0));
}
params.x = (initialX + (int) ((e2.getRawX() - initialTouchX)));
params.y = (initialY + (int) ((e2.getRawY() - initialTouchY)));
windowManager.updateViewLayout(mLinearLayout, params);
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.d(LOG_TAG, "onSingleTapConfirmed");
if (floaty.body.getVisibility() == View.GONE) {
params.x = metrics.widthPixels;
params.y = 0;
params.flags = params.flags & ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
//floaty.head.getLocationOnScreen(clickLocation);
floaty.head.setVisibility(View.GONE);
floaty.body.setVisibility(View.VISIBLE);
mLinearLayout.setBackgroundColor(Color.argb(200, 50, 50, 50));
} else {
floaty.body.setVisibility(View.GONE);
floaty.head.setVisibility(View.VISIBLE);
params.x = clickLocation[0];
params.y = clickLocation[1] - 36;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLinearLayout.setBackgroundColor(Color.argb(0, 0, 0, 0));
}
windowManager.updateViewLayout(mLinearLayout, params);
if (floaty.mOnHeadClickListener != null) {
floaty.mOnHeadClickListener.onClick(floaty.head);
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d(LOG_TAG, "onFling");
didFling = true;
int newX = params.x;
if (newX > (metrics.widthPixels / 2))
params.x = metrics.widthPixels;
else
params.x = 0;
windowManager.updateViewLayout(mLinearLayout, params);
return false;
}
});
mLinearLayout.setOrientation(LinearLayout.VERTICAL);
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP | Gravity.LEFT;
if (floaty.confChange) {
floaty.confChange = false;
if (floaty.oldX < (floaty.oldWidth / 2)) {
params.x = 0;
} else {
params.x = metrics.widthPixels;
}
params.y = (int) (metrics.heightPixels * floaty.ratioY);
} else {
params.x = metrics.widthPixels;
params.y = 0;
}
floaty.body.setVisibility(View.GONE);
floaty.head.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetectorCompat.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
floaty.head.setAlpha(1.0f);
if (!didFling) {
Log.d(LOG_TAG, "ACTION_UP");
int newX = params.x;
if (newX > (metrics.widthPixels / 2))
params.x = metrics.widthPixels;
else
params.x = 0;
windowManager.updateViewLayout(mLinearLayout, params);
}
}
return true;
}
});
windowManager.addView(mLinearLayout, params);
if (floaty.body.getParent() != null) {
((ViewGroup) floaty.body.getParent()).removeView(floaty.body);
}
mLinearLayout.setFocusable(true);
LinearLayout.LayoutParams headParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
LinearLayout.LayoutParams bodyParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
headParams.gravity = Gravity.TOP | Gravity.RIGHT;
bodyParams.gravity = Gravity.TOP;
mLinearLayout.addView(floaty.head, headParams);
mLinearLayout.addView(floaty.body, bodyParams);
}
public void onDestroy() {
super.onDestroy();
if (mLinearLayout != null) {
mLinearLayout.removeAllViews();
windowManager.removeView(mLinearLayout);
}
stopForeground(true);
}
}
}

View File

@@ -0,0 +1,410 @@
package com.stardust.view;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.view.GestureDetectorCompat;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.tool.ViewTool;
import com.stardust.widget.ViewSwitcher;
/**
* Created by Stardust on 2017/3/11.
*/
public class ResizableFloaty {
private static final String TAG = "ResizableFloaty";
private View mCollapsedView, mExpandedView;
private static ResizableFloaty floaty;
public ResizableFloaty(View collapsedView, View expandedView) {
mExpandedView = expandedView;
mCollapsedView = collapsedView;
}
public static void startService(Context context, View collapsedView, View expandedView) {
floaty = new ResizableFloaty(collapsedView, expandedView);
context.startService(new Intent(context, FloatingWindowService.class));
}
interface WindowBridge {
int getX();
int getY();
void updatePosition(int x, int y);
int getWidth();
int getHeight();
void updateMeasure(int width, int height);
int getScreenWidth();
int getScreenHeight();
}
public static class FloatingWindowService extends Service {
private WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowLayoutParams;
private RelativeLayout mWindowView;
private ViewSwitcher mCollapseExpandViewSwitcher;
private View mResizer;
private ResizableFloaty mFloaty = floaty;
private ResizeGesture mResizeGesture;
private DragGesture mDragGesture;
private ViewStack mViewStack = new ViewStack(new ViewStack.CurrentViewSetter() {
@Override
public void setCurrentView(View v) {
mCollapseExpandViewSwitcher.setSecondView(v);
}
});
private WindowBridge mWindowBridge = new WindowBridge() {
DisplayMetrics mDisplayMetrics;
@Override
public int getX() {
return mWindowLayoutParams.x;
}
@Override
public int getY() {
return mWindowLayoutParams.y;
}
@Override
public void updatePosition(int x, int y) {
mWindowLayoutParams.x = x;
mWindowLayoutParams.y = y;
mWindowManager.updateViewLayout(mWindowView, mWindowLayoutParams);
}
@Override
public int getWidth() {
return mWindowView.getWidth();
}
@Override
public int getHeight() {
return mWindowView.getHeight();
}
@Override
public void updateMeasure(int width, int height) {
mWindowLayoutParams.width = width;
mWindowLayoutParams.height = height;
mWindowManager.updateViewLayout(mWindowView, mWindowLayoutParams);
}
@Override
public int getScreenWidth() {
ensureDisplayMetrics();
return mDisplayMetrics.widthPixels;
}
@Override
public int getScreenHeight() {
ensureDisplayMetrics();
return mDisplayMetrics.heightPixels;
}
private void ensureDisplayMetrics() {
if (mDisplayMetrics == null) {
mDisplayMetrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(mDisplayMetrics);
}
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
mWindowLayoutParams = createWindowLayoutParams();
initWindowView();
initGesture();
setUpListeners();
}
private void setUpListeners() {
mDragGesture.setOnDraggedViewClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
enableWindowFocus();
expand();
}
});
mWindowView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
onBackPressed();
return true;
}
if (keyCode == KeyEvent.KEYCODE_HOME) {
onHomePressed();
return true;
}
return false;
}
});
}
private void expand() {
mCollapseExpandViewSwitcher.showSecond();
mResizeGesture.setResizeEnabled(true);
mDragGesture.setKeepToSide(false);
}
private void onBackPressed() {
if (mViewStack.canGoBack()) {
mViewStack.goBack();
} else {
collapse();
}
}
private void onHomePressed() {
mViewStack.goBackToFirst();
collapse();
}
private void collapse() {
mCollapseExpandViewSwitcher.showFirst();
disableWindowFocus();
mResizeGesture.setResizeEnabled(false);
mDragGesture.setKeepToSide(true);
}
private void disableWindowFocus() {
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mWindowManager.updateViewLayout(mWindowView, mWindowLayoutParams);
}
private void initGesture() {
mResizeGesture = ResizeGesture.enableResize(mResizer, mWindowBridge);
mDragGesture = DragGesture.enableDrag(mWindowView, mWindowBridge);
mResizeGesture.setResizeEnabled(false);
mDragGesture.setKeepToSide(true);
}
private void enableWindowFocus() {
mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mWindowManager.updateViewLayout(mWindowView, mWindowLayoutParams);
mWindowView.requestFocus();
}
private void initWindowView() {
mWindowView = (RelativeLayout) View.inflate(getApplicationContext(), R.layout.resizable_floaty_container, null);
mWindowView.setFocusableInTouchMode(true);
mCollapseExpandViewSwitcher = (ViewSwitcher) mWindowView.findViewById(R.id.container);
mResizer = mWindowView.findViewById(R.id.resizer);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mCollapseExpandViewSwitcher.addView(floaty.mCollapsedView, params);
mCollapseExpandViewSwitcher.addView(floaty.mExpandedView, params);
mViewStack.setRootView(floaty.mExpandedView);
mWindowManager.addView(mWindowView, mWindowLayoutParams);
}
private WindowManager.LayoutParams createWindowLayoutParams() {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
layoutParams.gravity = Gravity.TOP | Gravity.START;
return layoutParams;
}
}
public static class DragGesture extends GestureDetector.SimpleOnGestureListener {
public static DragGesture enableDrag(final View view, WindowBridge bridge) {
final DragGesture gestureListener = new DragGesture(bridge, view) {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
view.setAlpha(0.8f);
return super.onScroll(e1, e2, distanceX, distanceY);
}
};
final GestureDetectorCompat gestureDetector = new GestureDetectorCompat(view.getContext(), gestureListener);
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
view.setAlpha(1.0f);
if (!gestureListener.mFlung && gestureListener.isKeepToSide()) {
gestureListener.keepToSide();
}
}
return true;
}
});
return gestureListener;
}
private WindowBridge mWindowBridge;
private boolean mKeepToSide;
private View.OnClickListener mOnClickListener;
private View mView;
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
private boolean mFlung = false;
public DragGesture(WindowBridge windowBridge, View view) {
mWindowBridge = windowBridge;
mView = view;
}
public void setKeepToSide(boolean keepToSide) {
mKeepToSide = keepToSide;
}
public boolean isKeepToSide() {
return mKeepToSide;
}
@Override
public boolean onDown(MotionEvent event) {
initialX = mWindowBridge.getX();
initialY = mWindowBridge.getY();
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
mFlung = false;
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mWindowBridge.updatePosition(initialX + (int) ((e2.getRawX() - initialTouchX)),
initialY + (int) ((e2.getRawY() - initialTouchY)));
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mFlung = true;
if (mKeepToSide)
keepToSide();
return false;
}
public void keepToSide() {
int newX = mWindowBridge.getX();
if (newX > mWindowBridge.getScreenWidth() / 2)
mWindowBridge.updatePosition(mWindowBridge.getScreenWidth(), mWindowBridge.getY());
else
mWindowBridge.updatePosition(0, mWindowBridge.getY());
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (mOnClickListener != null)
mOnClickListener.onClick(mView);
return super.onSingleTapConfirmed(e);
}
public void setOnDraggedViewClickListener(View.OnClickListener onClickListener) {
mOnClickListener = onClickListener;
}
}
public static class ResizeGesture extends GestureDetector.SimpleOnGestureListener {
public static ResizeGesture enableResize(View resizer, WindowBridge windowBridge) {
ResizeGesture resizeGesture = new ResizeGesture(windowBridge, resizer);
final GestureDetector detector = new GestureDetector(resizer.getContext(), resizeGesture);
resizer.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
detector.onTouchEvent(event);
return true;
}
});
return resizeGesture;
}
private WindowBridge mWindowBridge;
private float initialTouchX;
private float initialTouchY;
private int mInitialWidth, mInitialHeight;
private View mResizerView;
private int mMinHeight = 200, mMinWidth = 200;
private final int mStatusBarHeight;
public ResizeGesture(WindowBridge windowBridge, View resizerView) {
mWindowBridge = windowBridge;
mResizerView = resizerView;
mStatusBarHeight = ViewTool.getStatusBarHeight(resizerView.getContext());
}
public void setMinHeight(int minHeight) {
mMinHeight = minHeight;
}
public void setMinWidth(int minWidth) {
mMinWidth = minWidth;
}
public void setResizeEnabled(boolean enabled) {
mResizerView.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
@Override
public boolean onDown(MotionEvent event) {
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
mInitialWidth = mWindowBridge.getWidth();
mInitialHeight = mWindowBridge.getHeight();
return false;
}
@Override
public boolean onScroll(MotionEvent e1, final MotionEvent e2, float distanceX, float distanceY) {
int newWidth = mInitialWidth + (int) ((e2.getRawX() - initialTouchX));
int newHeight = mInitialHeight + (int) ((e2.getRawY() - initialTouchY));
newWidth = Math.max(mMinWidth, newWidth);
newHeight = Math.max(mMinHeight, newHeight);
newWidth = Math.min(mWindowBridge.getScreenWidth() - mWindowBridge.getX() - mResizerView.getWidth(), newWidth);
newHeight = Math.min(mWindowBridge.getScreenHeight() - mWindowBridge.getY() - mResizerView.getHeight() - mStatusBarHeight, newHeight);
mWindowBridge.updateMeasure(newWidth, newHeight);
return true;
}
}
}

View File

@@ -0,0 +1,54 @@
package com.stardust.view;
import android.view.View;
import java.util.Stack;
/**
* Created by Stardust on 2017/3/11.
*/
public class ViewStack {
public interface CurrentViewSetter {
void setCurrentView(View v);
}
public interface NavigableView {
void goBack();
}
private Stack<View> mStack = new Stack<>();
private CurrentViewSetter mCurrentViewSetter;
public ViewStack(CurrentViewSetter currentViewSetter) {
mCurrentViewSetter = currentViewSetter;
}
public void navigateTo(View v) {
mStack.push(v);
mCurrentViewSetter.setCurrentView(v);
}
public boolean canGoBack() {
return mStack.size() > 1;
}
public void goBack() {
mCurrentViewSetter.setCurrentView(mStack.pop());
}
public void goBackToFirst() {
while (mStack.size() > 1) {
mStack.pop();
}
mCurrentViewSetter.setCurrentView(mStack.peek());
}
public void setRootView(View view) {
mStack.clear();
mStack.push(view);
}
}

View File

@@ -1,237 +0,0 @@
package com.stardust.view.accessibility;
/**
* Created by Stardust on 2017/3/6.
*/
import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.util.Xml;
import android.view.accessibility.AccessibilityNodeInfo;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
public class AccessibilityNodeInfoDumper {
private static final String LOGTAG = AccessibilityNodeInfoDumper.class.getSimpleName();
private static final String[] NAF_EXCLUDED_CLASSES = new String[]{
android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(),
android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName()
};
/**
* Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
* and generates an xml dump into the /data/local/window_dump.xml
*
* @param root The root accessibility node.
* @param rotation The rotaion of current display
* @param width The pixel width of current display
* @param height The pixel height of current display
*/
public static void dumpWindowToFile(AccessibilityNodeInfo root, int rotation,
int width, int height) {
File baseDir = new File(Environment.getDataDirectory(), "local");
if (!baseDir.exists()) {
baseDir.mkdir();
baseDir.setExecutable(true, false);
baseDir.setWritable(true, false);
baseDir.setReadable(true, false);
}
dumpWindowToFile(root,
new File(new File(Environment.getDataDirectory(), "local"), "window_dump.xml"),
rotation, width, height);
}
/**
* Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
* and generates an xml dump to the location specified by <code>dumpFile</code>
*
* @param root The root accessibility node.
* @param dumpFile The file to dump to.
* @param rotation The rotaion of current display
* @param width The pixel width of current display
* @param height The pixel height of current display
*/
public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile, int rotation,
int width, int height) {
if (root == null) {
return;
}
final long startTime = SystemClock.uptimeMillis();
try {
FileWriter writer = new FileWriter(dumpFile);
XmlSerializer serializer = Xml.newSerializer();
StringWriter stringWriter = new StringWriter();
serializer.setOutput(stringWriter);
serializer.startDocument("UTF-8", true);
serializer.startTag("", "hierarchy");
serializer.attribute("", "rotation", Integer.toString(rotation));
dumpNodeRec(root, serializer, 0, width, height);
serializer.endTag("", "hierarchy");
serializer.endDocument();
writer.write(stringWriter.toString());
writer.close();
} catch (IOException e) {
Log.e(LOGTAG, "failed to dump window to file", e);
}
final long endTime = SystemClock.uptimeMillis();
Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
}
private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer, int index,
int width, int height) throws IOException {
serializer.startTag("", "node");
if (!nafExcludedClass(node) && !nafCheck(node))
serializer.attribute("", "NAF", Boolean.toString(true));
serializer.attribute("", "index", Integer.toString(index));
serializer.attribute("", "text", safeCharSeqToString(node.getText()));
serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
serializer.attribute("", "password", Boolean.toString(node.isPassword()));
serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
node, width, height).toShortString());
int count = node.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
if (child.isVisibleToUser()) {
dumpNodeRec(child, serializer, i, width, height);
child.recycle();
} else {
Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
}
} else {
Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s",
i, count, node.toString()));
}
}
serializer.endTag("", "node");
}
/**
* The list of classes to exclude my not be complete. We're attempting to
* only reduce noise from standard layout classes that may be falsely
* configured to accept clicks and are also enabled.
*
* @param node
* @return true if node is excluded.
*/
private static boolean nafExcludedClass(AccessibilityNodeInfo node) {
String className = safeCharSeqToString(node.getClassName());
for (String excludedClassName : NAF_EXCLUDED_CLASSES) {
if (className.endsWith(excludedClassName))
return true;
}
return false;
}
/**
* We're looking for UI controls that are enabled, clickable but have no
* text nor content-description. Such controls configuration indicate an
* interactive control is present in the UI and is most likely not
* accessibility friendly. We refer to such controls here as NAF controls
* (Not Accessibility Friendly)
*
* @param node
* @return false if a node fails the check, true if all is OK
*/
private static boolean nafCheck(AccessibilityNodeInfo node) {
boolean isNaf = node.isClickable() && node.isEnabled()
&& safeCharSeqToString(node.getContentDescription()).isEmpty()
&& safeCharSeqToString(node.getText()).isEmpty();
if (!isNaf)
return true;
// check children since sometimes the containing element is clickable
// and NAF but a child's text or description is available. Will assume
// such layout as fine.
return childNafCheck(node);
}
/**
* This should be used when it's already determined that the node is NAF and
* a further check of its children is in order. A node maybe a container
* such as LinerLayout and may be set to be clickable but have no text or
* content description but it is counting on one of its children to fulfill
* the requirement for being accessibility friendly by having one or more of
* its children fill the text or content-description. Such a combination is
* considered by this dumper as acceptable for accessibility.
*
* @param node
* @return false if node fails the check.
*/
private static boolean childNafCheck(AccessibilityNodeInfo node) {
int childCount = node.getChildCount();
for (int x = 0; x < childCount; x++) {
AccessibilityNodeInfo childNode = node.getChild(x);
if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
|| !safeCharSeqToString(childNode.getText()).isEmpty())
return true;
if (childNafCheck(childNode))
return true;
}
return false;
}
private static String safeCharSeqToString(CharSequence cs) {
if (cs == null)
return "";
else {
return stripInvalidXMLChars(cs);
}
}
private static String stripInvalidXMLChars(CharSequence cs) {
StringBuffer ret = new StringBuffer();
char ch;
/* http://www.w3.org/TR/xml11/#charsets
[#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
[#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
[#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
[#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
[#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
[#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
[#x10FFFE-#x10FFFF].
*/
for (int i = 0; i < cs.length(); i++) {
ch = cs.charAt(i);
if ((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
(ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
(ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
(ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
(ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
(ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
(ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
(ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
(ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
(ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
(ch >= 0x10FFFE && ch <= 0x10FFFF))
ret.append(".");
else
ret.append(ch);
}
return ret.toString();
}
}

View File

@@ -1,37 +0,0 @@
package com.stardust.view.accessibility;
import android.graphics.Rect;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/3/6.
*/
public class AccessibilityNodeInfoHelper {
/**
* Returns the node's bounds clipped to the size of the display
*
* @param node
* @param width pixel width of the display
* @param height pixel height of the display
* @return null if node is null, else a Rect containing visible bounds
*/
public static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) {
if (node == null) {
return null;
}
// targeted node's bounds
Rect nodeRect = new Rect();
node.getBoundsInScreen(nodeRect);
Rect displayRect = new Rect();
displayRect.top = 0;
displayRect.left = 0;
displayRect.right = width;
displayRect.bottom = height;
boolean intersect = nodeRect.intersect(displayRect);
return nodeRect;
}
}

View File

@@ -1,69 +0,0 @@
package com.stardust.view.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.text.TextUtils;
import android.widget.Toast;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.tool.Shell;
/**
* Created by Stardust on 2017/1/26.
*/
public class AccessibilityServiceUtils {
public static void goToAccessibilitySetting(Context context) {
if (Pref.isFirstGoToAccessibilitySetting()) {
Toast.makeText(context, context.getString(R.string.text_please_choose) + context.getString(R.string._app_name), Toast.LENGTH_LONG).show();
}
context.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public static boolean isAccessibilityServiceEnabled(Context context, Class<? extends AccessibilityService> accessibilityService) {
ComponentName expectedComponentName = new ComponentName(context, accessibilityService);
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabledServicesSetting == null)
return false;
TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter(':');
colonSplitter.setString(enabledServicesSetting);
while (colonSplitter.hasNext()) {
String componentNameString = colonSplitter.next();
ComponentName enabledService = ComponentName.unflattenFromString(componentNameString);
if (enabledService != null && enabledService.equals(expectedComponentName))
return true;
}
return false;
}
public static boolean enableAccessibilityServiceByRootAndWaitFor(Context context, Class<? extends AccessibilityService> accessibilityService, long timeout) {
Shell.execCommand("settings put secure enabled_accessibility_services %accessibility:"
+ context.getPackageName() + "/" + accessibilityService.getName(), true);
long millis = System.currentTimeMillis();
while (true) {
if (isAccessibilityServiceEnabled(context, accessibilityService))
return true;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (System.currentTimeMillis() - millis >= timeout) {
return false;
}
}
}
}

View File

@@ -0,0 +1,63 @@
package com.stardust.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by Stardust on 2017/3/11.
*/
public class ViewSwitcher extends android.widget.ViewSwitcher {
private View mCurrentView;
public ViewSwitcher(Context context) {
super(context);
}
public ViewSwitcher(Context context, View first, View second) {
super(context);
addView(first);
addView(second);
}
public ViewSwitcher(Context context, AttributeSet attrs) {
super(context, attrs);
}
public View getCurrentView() {
return mCurrentView;
}
public int getCurrentViewIndex() {
return mCurrentView == getChildAt(0) ? 0 : 1;
}
public void showFirst() {
ensureCurrentView();
if (mCurrentView != getChildAt(0)) {
showPrevious();
mCurrentView = getChildAt(0);
}
}
private void ensureCurrentView() {
if (mCurrentView == null) {
mCurrentView = getChildAt(0);
}
}
public void showSecond() {
ensureCurrentView();
if (mCurrentView != getChildAt(1)) {
showNext();
mCurrentView = getChildAt(1);
}
}
public void setSecondView(View v) {
removeViewAt(1);
addView(v);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="0dp"
android:shape="ring"
android:thicknessRatio="2"
android:useLevel="false">
<solid android:color="@android:color/white"/>
</shape>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="0dp"
android:shape="ring"
android:thicknessRatio="2"
android:useLevel="false">
<solid android:color="@android:color/white"/>
<stroke
android:width="1dp"
android:color="@android:color/black"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More