v1.0
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
6
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
18
.idea/deploymentTargetSelector.xml
generated
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-03-15T16:12:34.889094100Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="Default" identifier="serial=192.168.1.5:5555;connection=3abe3b7e" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
20
.idea/gradle.xml
generated
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
10
.idea/migrations.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
9
.idea/misc.xml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
17
.idea/runConfigurations.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# SuperMousePointer 
|
||||
|
||||
A virtual mouse for legacy Android which can be used in desired apps via the arrow navigation keys.
|
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
41
app/build.gradle
Normal file
@ -0,0 +1,41 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'org.eu.octt.supermousepointer'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.eu.octt.supermousepointer"
|
||||
minSdk 16
|
||||
//noinspection ExpiredTargetSdkVersion
|
||||
targetSdk 19
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.outputFileName = ("WhatsApp.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {}
|
21
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
30
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="19" android:versionName="1.0" package="org.eu.octt.supermousepointer">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" tools:ignore="ExpiredTargetSdkVersion" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.INJECT_EVENTS" tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.GET_TASKS"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" tools:ignore="ProtectedPermissions" />
|
||||
<application android:name=".MyApplication" android:label="@string/app_name" android:allowBackup="true" android:supportsRtl="true" android:icon="@mipmap/ic_launcher">
|
||||
<service android:label="@string/app_name" android:name="org.eu.octt.supermousepointer.KeyEventProcessService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:enabled="true" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.accessibilityservice" android:resource="@xml/keyevent_service_config"/>
|
||||
</service>
|
||||
<receiver android:name="org.eu.octt.supermousepointer.MouserPointerReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<activity android:name=".SettingsActivity" android:label="@string/settings_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,129 @@
|
||||
package org.eu.octt.supermousepointer;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AppListFragment extends Fragment {
|
||||
private ListView listView;
|
||||
private AppListAdapter adapter;
|
||||
private List<AppInfo> appList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_app_list, container, false);
|
||||
listView = view.findViewById(R.id.appList);
|
||||
|
||||
// Keypad navigation setup
|
||||
listView.setItemsCanFocus(true);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
|
||||
new LoadAppsTask().execute();
|
||||
|
||||
listView.setOnItemClickListener((parent, view1, position, id) -> {
|
||||
cycleAppState(position);
|
||||
listView.setItemChecked(position, true);
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void cycleAppState(int position) {
|
||||
AppInfo app = appList.get(position);
|
||||
app.state = (app.state + 1) % 3;
|
||||
adapter.notifyDataSetChanged();
|
||||
AppStateManager.saveState(app.packageName, app.state);
|
||||
}
|
||||
|
||||
private class LoadAppsTask extends AsyncTask<Void, Void, List<AppInfo>> {
|
||||
@Override
|
||||
protected List<AppInfo> doInBackground(Void... voids) {
|
||||
List<AppInfo> apps = new ArrayList<>();
|
||||
PackageManager pm = getActivity().getPackageManager();
|
||||
|
||||
for (PackageInfo pkg : pm.getInstalledPackages(0)) {
|
||||
AppInfo app = new AppInfo();
|
||||
app.packageName = pkg.packageName;
|
||||
app.label = pkg.applicationInfo.loadLabel(pm).toString();
|
||||
app.icon = pkg.applicationInfo.loadIcon(pm);
|
||||
app.state = AppStateManager.getState(app.packageName);
|
||||
apps.add(app);
|
||||
}
|
||||
|
||||
Collections.sort(apps, (a1, a2) -> a1.label.compareToIgnoreCase(a2.label));
|
||||
return apps;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<AppInfo> result) {
|
||||
appList.clear();
|
||||
appList.addAll(result);
|
||||
adapter = new AppListAdapter(getActivity(), appList);
|
||||
listView.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
private class AppListAdapter extends ArrayAdapter<AppInfo> {
|
||||
private final int[] stateIcons = {
|
||||
R.drawable.ic_state_default,
|
||||
R.drawable.ic_state_whitelist,
|
||||
R.drawable.ic_state_blacklist
|
||||
};
|
||||
|
||||
AppListAdapter(Context context, List<AppInfo> apps) {
|
||||
super(context, R.layout.list_item_app, apps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||
holder = new ViewHolder();
|
||||
holder.icon = convertView.findViewById(R.id.icon);
|
||||
holder.label = convertView.findViewById(R.id.label);
|
||||
holder.state = convertView.findViewById(R.id.state);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
AppInfo app = getItem(position);
|
||||
holder.icon.setImageDrawable(app.icon);
|
||||
holder.label.setText(app.label);
|
||||
holder.state.setImageResource(stateIcons[app.state]);
|
||||
|
||||
convertView.setContentDescription(app.label + ", " + getString(AppStateManager.getStateDescription(app.state)));
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
class ViewHolder {
|
||||
ImageView icon;
|
||||
TextView label;
|
||||
ImageView state;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AppInfo {
|
||||
Drawable icon;
|
||||
String packageName;
|
||||
String label;
|
||||
int state; // 0 = default, 1 = whitelisted, 2 = blacklisted
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.eu.octt.supermousepointer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AppStateManager {
|
||||
private static final String PREFS_NAME = "AppStates";
|
||||
private static final String KEY_POINTER_STYLE = "pointerStyle";
|
||||
private static final String DEFAULT_POINTER = "pointer_arrow";
|
||||
private static SharedPreferences prefs;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void saveState(String packageName, @AppState int state) {
|
||||
prefs.edit().putInt(packageName, state).apply();
|
||||
}
|
||||
|
||||
@AppState
|
||||
public static int getState(String packageName) {
|
||||
return prefs.getInt(packageName, AppState.DEFAULT);
|
||||
}
|
||||
|
||||
// public static boolean isWhitelisted(String packageName) {
|
||||
// return getState(packageName) == AppState.WHITELISTED;
|
||||
// }
|
||||
//
|
||||
// public static boolean isBlacklisted(String packageName) {
|
||||
// return getState(packageName) == AppState.BLACKLISTED;
|
||||
// }
|
||||
|
||||
public static int getStateDescription(@AppState int state) {
|
||||
switch (state) {
|
||||
case AppState.WHITELISTED: return R.string.state_whitelisted;
|
||||
case AppState.BLACKLISTED: return R.string.state_blacklisted;
|
||||
default: return R.string.state_default;
|
||||
}
|
||||
}
|
||||
|
||||
public @interface AppState {
|
||||
int DEFAULT = 0;
|
||||
int WHITELISTED = 1;
|
||||
int BLACKLISTED = 2;
|
||||
}
|
||||
|
||||
public static void savePointerStyle(String resName) {
|
||||
prefs.edit().putString(KEY_POINTER_STYLE, resName).apply();
|
||||
if (styleListener != null) {
|
||||
styleListener.onPointerStyleChanged(resName);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSelectedPointerStyle() {
|
||||
return prefs.getString(KEY_POINTER_STYLE, DEFAULT_POINTER);
|
||||
}
|
||||
|
||||
|
||||
public static int getPointerDrawableId(Context context, String resName) {
|
||||
int resId = context.getResources().getIdentifier(resName, "drawable", context.getPackageName());
|
||||
return resId != 0 ? resId : R.drawable.pointer_arrow;
|
||||
}
|
||||
|
||||
private static PointerStyleChangeListener styleListener;
|
||||
|
||||
public interface PointerStyleChangeListener {
|
||||
void onPointerStyleChanged(String newDrawableId);
|
||||
}
|
||||
|
||||
public static void setPointerStyleListener(PointerStyleChangeListener listener) {
|
||||
styleListener = listener;
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package org.eu.octt.supermousepointer;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class CursorStyleFragment extends Fragment {
|
||||
|
||||
// Define CursorStyle class with resource name
|
||||
public static class CursorStyle {
|
||||
private final String resName;
|
||||
private final String displayName;
|
||||
|
||||
public CursorStyle(String resName, String displayName) {
|
||||
this.resName = resName;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getResName() {
|
||||
return resName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
|
||||
private ListView listView;
|
||||
private CursorStyleAdapter adapter;
|
||||
private int selectedPosition = 0;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_cursor_style, container, false);
|
||||
listView = view.findViewById(R.id.cursorStyleList);
|
||||
|
||||
// Create cursor styles with resource names
|
||||
List<CursorStyle> styles = Arrays.asList(
|
||||
new CursorStyle("pointer_arrow", "Default"),
|
||||
new CursorStyle("pointer_arrow_inverted", "Inverted"),
|
||||
new CursorStyle("pointer_arrow_app", "SuperMousePointer")
|
||||
);
|
||||
|
||||
// Find initial selection position
|
||||
String selectedResName = AppStateManager.getSelectedPointerStyle();
|
||||
for (int i = 0; i < styles.size(); i++) {
|
||||
if (styles.get(i).getResName().equals(selectedResName)) {
|
||||
selectedPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
adapter = new CursorStyleAdapter(getActivity(), styles);
|
||||
listView.setAdapter(adapter);
|
||||
listView.setSelection(selectedPosition);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
|
||||
listView.setOnItemClickListener((parent, view1, position, id) -> {
|
||||
if (position != selectedPosition) {
|
||||
selectedPosition = position;
|
||||
CursorStyle selectedStyle = adapter.getItem(position);
|
||||
AppStateManager.savePointerStyle(selectedStyle.getResName());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private class CursorStyleAdapter extends ArrayAdapter<CursorStyle> {
|
||||
CursorStyleAdapter(Context context, List<CursorStyle> styles) {
|
||||
super(context, R.layout.list_item_cursor_style, styles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item_cursor_style, parent, false);
|
||||
holder = new ViewHolder();
|
||||
holder.radio = convertView.findViewById(R.id.radioButton);
|
||||
holder.image = convertView.findViewById(R.id.pointerImage);
|
||||
holder.label = convertView.findViewById(R.id.pointerName);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
CursorStyle style = getItem(position);
|
||||
|
||||
holder.radio.setChecked(position == selectedPosition);
|
||||
holder.image.setImageResource(AppStateManager.getPointerDrawableId(getContext(), style.getResName()));
|
||||
holder.label.setText(style.getDisplayName());
|
||||
holder.label.setText(style.getDisplayName());
|
||||
holder.image.setImageResource(
|
||||
AppStateManager.getPointerDrawableId(getContext(), style.getResName())
|
||||
);
|
||||
holder.radio.setChecked(position == selectedPosition);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
class ViewHolder {
|
||||
ImageView image;
|
||||
RadioButton radio;
|
||||
TextView label;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
package org.eu.octt.supermousepointer;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import org.eu.octt.supermousepointer.utils.InjectEventUtil;
|
||||
import org.eu.octt.supermousepointer.utils.PackageUtil;
|
||||
import org.eu.octt.supermousepointer.window.MousePointer;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class KeyEventProcessService extends AccessibilityService implements AppStateManager.PointerStyleChangeListener {
|
||||
@SuppressLint("HandlerLeak") // this can't be made static or it won't work
|
||||
private final Handler mHandler = new Handler() {
|
||||
@Override // android.os.Handler
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case 0:
|
||||
KeyEventProcessService.this.mMousePointer.moveMouse(KeyEventProcessService.this.mOffsetX, KeyEventProcessService.this.mOffsetY);
|
||||
return;
|
||||
case 1:
|
||||
KeyEventProcessService.this.updateMousePointerOffset((KeyEvent) message.obj);
|
||||
KeyEventProcessService.this.animateMousePointer();
|
||||
return;
|
||||
case 2:
|
||||
KeyEventProcessService.this.injectMoveTouchEvent();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
private MousePointer mMousePointer;
|
||||
private float mOffsetX;
|
||||
private float mOffsetY;
|
||||
private long mOkDownTime;
|
||||
private Timer mTimer;
|
||||
private TimerTask mTimerTask;
|
||||
private CharSequence mUseWindowPackageName;
|
||||
private static KeyEventProcessService instance;
|
||||
|
||||
@Override // android.accessibilityservice.AccessibilityService
|
||||
protected boolean onKeyEvent(KeyEvent event) {
|
||||
int action = event.getAction();
|
||||
if (action == 1) {
|
||||
cancelLongPress();
|
||||
}
|
||||
boolean isIntercept = isNeedToInterceptNavigationKeyEvent(event) || isNeedToInterceptOKKeyEvent(event);
|
||||
return isIntercept || super.onKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override // android.accessibilityservice.AccessibilityService
|
||||
public void onInterrupt() {
|
||||
Log.i("MousePointer", "KeyEventProcessService::onInterrupt");
|
||||
}
|
||||
|
||||
@Override // android.app.Service
|
||||
public void onCreate() {
|
||||
Log.i("MousePointer", "KeyEventProcessService::onCreate");
|
||||
super.onCreate();
|
||||
this.mMousePointer = new MousePointer(getApplicationContext());
|
||||
this.mMousePointer.hideMouse();
|
||||
PackageUtil.updateEnabled3rdPreloadedApps(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override // android.app.Service
|
||||
public boolean onUnbind(Intent intent) {
|
||||
Log.i("MousePointer", "KeyEventProcessService::onUnbind");
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
|
||||
public static KeyEventProcessService getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override // android.accessibilityservice.AccessibilityService
|
||||
protected void onServiceConnected() {
|
||||
instance = this;
|
||||
AppStateManager.setPointerStyleListener(this);
|
||||
Log.i("MousePointer", "config success!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
instance = null;
|
||||
AppStateManager.setPointerStyleListener(null);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override // android.accessibilityservice.AccessibilityService
|
||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||
int eventType = event.getEventType();
|
||||
switch (eventType) {
|
||||
case 16:
|
||||
this.mMousePointer.hideMouse();
|
||||
return;
|
||||
case 32:
|
||||
Log.d("MousePointer", " TYPE_WINDOW_STATE_CHANGED " + ((Object) event.getPackageName()));
|
||||
this.mUseWindowPackageName = event.getPackageName();
|
||||
updateMouseVisibility();
|
||||
updateAccessibilityService();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNeedToInterceptNavigationKeyEvent(KeyEvent keyEvent) {
|
||||
int keyCode = keyEvent.getKeyCode();
|
||||
int action = keyEvent.getAction();
|
||||
if (!PackageUtil.shouldPointerBeHidden(this.mUseWindowPackageName, getApplicationContext()) && (keyCode == 19 || keyCode == 20 || keyCode == 21 || keyCode == 22)) {
|
||||
this.mMousePointer.showMouse();
|
||||
updateMousePointerOffset(keyEvent);
|
||||
if (!this.mMousePointer.isOnMouseMovementRangeBoundary(this.mOffsetX, this.mOffsetY)) {
|
||||
if (action == 0) {
|
||||
this.mMousePointer.moveMouse(this.mOffsetX, this.mOffsetY);
|
||||
triggerLongPressDelayed(keyEvent);
|
||||
}
|
||||
Log.d("MousePointer", "isNeedToInterceptNavigationKeyEvent true");
|
||||
return true;
|
||||
} else if ("com.facebook.lite".equals(this.mUseWindowPackageName)) {
|
||||
InjectEventUtil.injectInputTouchEventForMove(getApplicationContext(), this.mOffsetY);
|
||||
Log.d("MousePointer", "isNeedToInterceptNavigationKeyEvent scroll true");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isNeedToInterceptOKKeyEvent(KeyEvent keyEvent) {
|
||||
int keyCode = keyEvent.getKeyCode();
|
||||
int action = keyEvent.getAction();
|
||||
if (this.mMousePointer.isShowing() && !PackageUtil.shouldPointerBeHidden(this.mUseWindowPackageName, getApplicationContext()) && keyCode == 66) {
|
||||
if (action == 0) {
|
||||
this.mOkDownTime = SystemClock.uptimeMillis();
|
||||
this.mHandler.removeMessages(2);
|
||||
InjectEventUtil.injectDownTouchEvent(getApplicationContext(), this.mOkDownTime, this.mMousePointer.getMousePointerX(), this.mMousePointer.getMousePointerY());
|
||||
Log.d("MousePointer", "isNeedToInterceptOKKeyEvent down");
|
||||
this.mHandler.sendEmptyMessageDelayed(2, 30L);
|
||||
} else if (action == 1) {
|
||||
InjectEventUtil.injectUpTouchEvent(getApplicationContext(), this.mOkDownTime, this.mMousePointer.getMousePointerX(), this.mMousePointer.getMousePointerY());
|
||||
Log.d("MousePointer", "isNeedToInterceptOKKeyEvent up");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void injectMoveTouchEvent() {
|
||||
InjectEventUtil.injectMoveTouchEvent(getApplicationContext(), this.mOkDownTime, this.mMousePointer.getMousePointerX() + 3.0f, this.mMousePointer.getMousePointerY() + 3.0f);
|
||||
}
|
||||
|
||||
private void updateMouseVisibility() {
|
||||
if (PackageUtil.shouldPointerBeHidden(this.mUseWindowPackageName, getApplicationContext())) {
|
||||
this.mMousePointer.hideMouse();
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerLongPressDelayed(KeyEvent event) {
|
||||
cancelLongPress();
|
||||
Message message = Message.obtain();
|
||||
message.obj = event;
|
||||
message.what = 1;
|
||||
this.mHandler.sendMessageDelayed(message, 300L);
|
||||
}
|
||||
|
||||
private void cancelLongPress() {
|
||||
stopAnimateMousePointer();
|
||||
this.mHandler.removeMessages(1);
|
||||
}
|
||||
|
||||
public void updateMousePointerOffset(KeyEvent event) {
|
||||
if (event.getKeyCode() == 19) {
|
||||
this.mOffsetX = 0.0f;
|
||||
this.mOffsetY = -(PackageUtil.getDensity(getApplicationContext()) * 10.0f);
|
||||
} else if (event.getKeyCode() == 20) {
|
||||
this.mOffsetX = 0.0f;
|
||||
this.mOffsetY = PackageUtil.getDensity(getApplicationContext()) * 10.0f;
|
||||
} else if (event.getKeyCode() == 21) {
|
||||
this.mOffsetX = -(PackageUtil.getDensity(getApplicationContext()) * 10.0f);
|
||||
this.mOffsetY = 0.0f;
|
||||
} else if (event.getKeyCode() == 22) {
|
||||
this.mOffsetX = PackageUtil.getDensity(getApplicationContext()) * 10.0f;
|
||||
this.mOffsetY = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void animateMousePointer() {
|
||||
if (this.mOffsetX != 0.0f || this.mOffsetY != 0.0f) {
|
||||
this.mTimer = new Timer();
|
||||
this.mTimerTask = new TimerTask() { // from class: org.eu.octt.supermousepointer.KeyEventProcessService.2
|
||||
@Override // java.util.TimerTask, java.lang.Runnable
|
||||
public void run() {
|
||||
KeyEventProcessService.this.mHandler.sendEmptyMessage(0);
|
||||
}
|
||||
};
|
||||
this.mTimer.schedule(this.mTimerTask, 0L, 10L);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAnimateMousePointer() {
|
||||
if (this.mTimer != null) {
|
||||
this.mTimer.cancel();
|
||||
}
|
||||
if (this.mTimerTask != null) {
|
||||
this.mTimerTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAccessibilityService() {
|
||||
AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
|
||||
accessibilityServiceInfo.eventTypes = -1;
|
||||
accessibilityServiceInfo.feedbackType = 16;
|
||||
accessibilityServiceInfo.notificationTimeout = 100L;
|
||||
if (PackageUtil.shouldPointerBeHidden(this.mUseWindowPackageName, getApplicationContext())) {
|
||||
accessibilityServiceInfo.flags = 1;
|
||||
} else {
|
||||
accessibilityServiceInfo.flags = 32;
|
||||
}
|
||||
setServiceInfo(accessibilityServiceInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerStyleChanged(String resName) {
|
||||
this.mMousePointer.setImageResource(AppStateManager.getPointerDrawableId(this, resName));
|
||||
}
|
||||
|
||||
private AccessibilityNodeInfo trackedNode;
|
||||
|
||||
public void setTrackedNode(AccessibilityNodeInfo node) {
|
||||
if (trackedNode != null) trackedNode.recycle();
|
||||
trackedNode = AccessibilityNodeInfo.obtain(node);
|
||||
}
|
||||
|
||||
public AccessibilityNodeInfo getTrackedNode() {
|
||||
return trackedNode != null ? AccessibilityNodeInfo.obtain(trackedNode) : null;
|
||||
}
|
||||
|
||||
public void clearTrackedNode() {
|
||||
if (trackedNode != null) {
|
||||
trackedNode.recycle();
|
||||
trackedNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
// public void updatePointerPosition(int x, int y) {
|
||||
// runOnUiThread(() -> {
|
||||
// FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mousePointer.getLayoutParams();
|
||||
// params.leftMargin = x;
|
||||
// params.topMargin = y;
|
||||
// mousePointer.setLayoutParams(params);
|
||||
// });
|
||||
// }
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package org.eu.octt.supermousepointer;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
public class MouserPointerReceiver extends BroadcastReceiver {
|
||||
@Override // android.content.BroadcastReceiver
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
enableKeyEventProcessService(context);
|
||||
}
|
||||
|
||||
private void enableKeyEventProcessService(Context context) {
|
||||
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), "enabled_accessibility_services");
|
||||
Log.d("MousePointer", "enableKeyEventProcessService Accessibility services :: " + enabledServicesSetting);
|
||||
if (enabledServicesSetting == null || enabledServicesSetting.equals("")) {
|
||||
updateAccessibilityServices("org.eu.octt.supermousepointer/com.qualcomm.qti.mousepointer.KeyEventProcessService", context);
|
||||
} else if (!enabledServicesSetting.contains("org.eu.octt.supermousepointer/com.qualcomm.qti.mousepointer.KeyEventProcessService")) {
|
||||
updateAccessibilityServices(enabledServicesSetting + ":org.eu.octt.supermousepointer/com.qualcomm.qti.mousepointer.KeyEventProcessService", context);
|
||||
}
|
||||
disableBootReceiver(context);
|
||||
Log.d("MousePointer", "boot completed");
|
||||
}
|
||||
|
||||
private void updateAccessibilityServices(String enabledServicesSetting, Context context) {
|
||||
Settings.Secure.putString(context.getContentResolver(), "enabled_accessibility_services", enabledServicesSetting);
|
||||
Settings.Secure.putInt(context.getContentResolver(), "accessibility_enabled", 1);
|
||||
}
|
||||
|
||||
private void disableBootReceiver(Context context) {
|
||||
context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, MouserPointerReceiver.class), 2, 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.eu.octt.supermousepointer;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class MyApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
AppStateManager.initialize(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package org.eu.octt.supermousepointer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SettingsActivity extends Activity {
|
||||
private FrameLayout contentFrame;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_settings);
|
||||
getActionBar().setTitle(R.string.settings_title);
|
||||
contentFrame = findViewById(R.id.contentFrame);
|
||||
showDefaultTab();
|
||||
|
||||
((RadioGroup)findViewById(R.id.tabGroup)).setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (checkedId == R.id.tabApps) {
|
||||
showFragment(new AppListFragment());
|
||||
} else {
|
||||
showFragment(new CursorStyleFragment());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showDefaultTab() {
|
||||
((RadioButton)findViewById(R.id.tabApps)).setChecked(true);
|
||||
showFragment(new AppListFragment());
|
||||
}
|
||||
|
||||
private void showFragment(Fragment fragment) {
|
||||
getFragmentManager().beginTransaction().replace(R.id.contentFrame, fragment).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.settings_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_info) {
|
||||
showAboutDialog();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
RadioGroup tabs = findViewById(R.id.tabGroup);
|
||||
int currentTab = tabs.indexOfChild(tabs.findViewById(tabs.getCheckedRadioButtonId()));
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
if(currentTab > 0) {
|
||||
((RadioButton)tabs.getChildAt(currentTab - 1)).setChecked(true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
if(currentTab < 1) {
|
||||
((RadioButton)tabs.getChildAt(currentTab + 1)).setChecked(true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private void showAboutDialog() {
|
||||
try {
|
||||
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
View view = LayoutInflater.from(this).inflate(R.layout.dialog_about, null);
|
||||
|
||||
((ImageView)view.findViewById(R.id.appIcon)).setImageDrawable(getPackageManager().getApplicationIcon(getPackageName()));
|
||||
((TextView)view.findViewById(R.id.appName)).setText(getPackageManager().getApplicationLabel(getApplicationInfo()));
|
||||
((TextView)view.findViewById(R.id.appVersion)).setText("v" + pInfo.versionName);
|
||||
((TextView)view.findViewById(R.id.appDescription)).setText(R.string.accessibility_description);
|
||||
|
||||
SpannableString links = new SpannableString("Official Pages:" +
|
||||
"\nhttps://gitlab.com/octospacc/SuperMousePointer" +
|
||||
"\nhttps://github.com/octospacc/SuperMousePointer" +
|
||||
"\nhttps://gitea.it/octospacc/SuperMousePointer");
|
||||
Linkify.addLinks(links, Linkify.WEB_URLS);
|
||||
TextView linksView = view.findViewById(R.id.links);
|
||||
linksView.setText(links);
|
||||
linksView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
builder.setView(view).setPositiveButton(android.R.string.ok, null).show();
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
package org.eu.octt.supermousepointer.utils;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.InputEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import org.eu.octt.supermousepointer.KeyEventProcessService;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.Key;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
|
||||
public class InjectEventUtil {
|
||||
public static void injectDownTouchEvent(Context context, long downTime, float x, float y) {
|
||||
injectTouchEvent(context, downTime, SystemClock.uptimeMillis(), 0, x, y);
|
||||
}
|
||||
|
||||
public static void injectMoveTouchEvent(Context context, long downTime, float x, float y) {
|
||||
injectTouchEvent(context, downTime, SystemClock.uptimeMillis(), 2, x, y);
|
||||
}
|
||||
|
||||
public static void injectUpTouchEvent(Context context, long downTime, float x, float y) {
|
||||
injectTouchEvent(context, downTime, SystemClock.uptimeMillis(), 1, x, y);
|
||||
}
|
||||
|
||||
public static void injectInputTouchEventForMove(Context context, float offsetY) {
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
long downTime = SystemClock.uptimeMillis();
|
||||
injectTouchEvent(context, downTime, downTime, 0, dm.widthPixels / 2, dm.heightPixels / 2);
|
||||
injectTouchEvent(context, downTime, downTime + 20, 2, dm.widthPixels / 2, (dm.heightPixels / 2) - offsetY);
|
||||
injectTouchEvent(context, downTime, downTime + 20, 1, dm.widthPixels / 2, (dm.heightPixels / 2) - offsetY);
|
||||
}
|
||||
|
||||
private static void injectTouchEvent(Context context, long downTime, long eventTime,
|
||||
int action, float x, float y) {
|
||||
|
||||
var service = KeyEventProcessService.getInstance();
|
||||
if (service == null) return;
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
handleTouchDown(service, x, y);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
handleTouchMove(service, x, y);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
handleTouchUp(service);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleTouchDown(KeyEventProcessService service, float x, float y) {
|
||||
updateLastCoordinates(x, y);
|
||||
AccessibilityNodeInfo root = service.getRootInActiveWindow();
|
||||
if (root != null) {
|
||||
AccessibilityNodeInfo node = findDeepestNodeAt(root, (int) x, (int) y);
|
||||
if (node != null) {
|
||||
service.setTrackedNode(node);
|
||||
node.recycle();
|
||||
}
|
||||
root.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private static float lastX, lastY;
|
||||
|
||||
public static void updateLastCoordinates(float x, float y) {
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
private static void handleTouchMove(KeyEventProcessService service, float x, float y) {
|
||||
// AccessibilityNodeInfo current = service.getTrackedNode();
|
||||
// if (current != null) {
|
||||
// if (current.isScrollable()) {
|
||||
// current.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
|
||||
// }
|
||||
// current.recycle();
|
||||
// }
|
||||
updateLastCoordinates(x, y);
|
||||
}
|
||||
|
||||
private static void handleTouchUp(KeyEventProcessService service) {
|
||||
boolean clickSuccess = false;
|
||||
AccessibilityNodeInfo node = service.getTrackedNode();
|
||||
if (node != null) {
|
||||
if (node.isClickable()) {
|
||||
clickSuccess = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
}
|
||||
service.clearTrackedNode();
|
||||
node.recycle();
|
||||
}
|
||||
if (!clickSuccess) {
|
||||
try {
|
||||
Class<?> argsClass = AccessibilityNodeInfo.class;
|
||||
@SuppressLint("PrivateApi")
|
||||
Field xField = argsClass.getDeclaredField("ACTION_ARGUMENT_X_INT");
|
||||
@SuppressLint("PrivateApi")
|
||||
Field yField = argsClass.getDeclaredField("ACTION_ARGUMENT_Y_INT");
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt((String)xField.get(null), (int)lastX);
|
||||
args.putInt((String)yField.get(null), (int)lastY);
|
||||
|
||||
AccessibilityNodeInfo root = service.getRootInActiveWindow();
|
||||
if (root != null) {
|
||||
root.performAction(AccessibilityNodeInfo.ACTION_CLICK, args);
|
||||
root.recycle();
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// private static void injectTouchEvent(Context context, long downTime, long eventTime, int action, float x, float y) {
|
||||
//// MotionEvent motionEvent = MotionEvent.obtain(downTime, eventTime, action, x, y, 0);
|
||||
//// motionEvent.setSource(4098);
|
||||
//// InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||
//// try {
|
||||
//// im.getClass().getMethod("injectInputEvent", InputEvent.class, int.class).invoke(im, motionEvent, 0);
|
||||
//// } catch (ReflectiveOperationException e) {
|
||||
//// throw new RuntimeException(e);
|
||||
//// }
|
||||
//
|
||||
// var service = KeyEventProcessService.getInstance();
|
||||
// if (service == null) {
|
||||
// Log.e("InjectTouch", "Accessibility service not available");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// AccessibilityNodeInfo rootNode = service.getRootInActiveWindow();
|
||||
// if (rootNode == null) {
|
||||
// Log.e("InjectTouch", "No root node available");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Find the deepest node at coordinates
|
||||
// AccessibilityNodeInfo targetNode = findDeepestNodeAt(rootNode, (int) x, (int) y);
|
||||
// if (targetNode == null) {
|
||||
// Log.e("InjectTouch", "No node found at coordinates (" + x + "," + y + ")");
|
||||
// rootNode.recycle();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Verify node is visible and actionable
|
||||
// if (!isNodeActionable(targetNode)) {
|
||||
// Log.e("InjectTouch", "Node not actionable: " + targetNode);
|
||||
// targetNode.recycle();
|
||||
// rootNode.recycle();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Perform the click
|
||||
// boolean result = performNodeClick(targetNode);
|
||||
// Log.d("InjectTouch", (result ? "Success" : "Failed") + " clicking node: " + targetNode);
|
||||
//
|
||||
// targetNode.recycle();
|
||||
// rootNode.recycle();
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// Log.e("InjectTouch", "Error handling touch event", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
private static AccessibilityNodeInfo findDeepestNodeAt(AccessibilityNodeInfo root, int x, int y) {
|
||||
if (root == null) return null;
|
||||
|
||||
ArrayDeque<AccessibilityNodeInfo> queue = new ArrayDeque<>();
|
||||
queue.add(root);
|
||||
AccessibilityNodeInfo deepestNode = null;
|
||||
int maxDepth = -1;
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
AccessibilityNodeInfo current = queue.poll();
|
||||
try {
|
||||
Rect bounds = new Rect();
|
||||
current.getBoundsInScreen(bounds);
|
||||
|
||||
if (bounds.contains(x, y)) {
|
||||
// Track deepest node
|
||||
int depth = getNodeDepth(current);
|
||||
if (depth > maxDepth) {
|
||||
if (deepestNode != null) deepestNode.recycle();
|
||||
deepestNode = AccessibilityNodeInfo.obtain(current);
|
||||
maxDepth = depth;
|
||||
}
|
||||
|
||||
// Add children to queue
|
||||
for (int i = 0; i < current.getChildCount(); i++) {
|
||||
AccessibilityNodeInfo child = current.getChild(i);
|
||||
if (child != null) {
|
||||
queue.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (current != root) {
|
||||
current.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
return deepestNode;
|
||||
}
|
||||
|
||||
private static int getNodeDepth(AccessibilityNodeInfo node) {
|
||||
int depth = 0;
|
||||
while (node != null) {
|
||||
depth++;
|
||||
node = node.getParent();
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
private static boolean isNodeActionable(AccessibilityNodeInfo node) {
|
||||
if (node == null) return false;
|
||||
|
||||
return (node.isClickable() || node.isLongClickable() || node.isFocusable())
|
||||
&& node.isEnabled()
|
||||
&& node.isVisibleToUser();
|
||||
}
|
||||
|
||||
private static boolean performNodeClick(AccessibilityNodeInfo node) {
|
||||
if (node.isClickable()) {
|
||||
return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
}
|
||||
|
||||
// Try parents up the hierarchy
|
||||
AccessibilityNodeInfo parent = node.getParent();
|
||||
while (parent != null) {
|
||||
if (parent.isClickable()) {
|
||||
boolean result = parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
parent.recycle();
|
||||
return result;
|
||||
}
|
||||
AccessibilityNodeInfo oldParent = parent;
|
||||
parent = parent.getParent();
|
||||
oldParent.recycle();
|
||||
}
|
||||
|
||||
// Fallback to accessibility focus
|
||||
return node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) && node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.eu.octt.supermousepointer.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import org.eu.octt.supermousepointer.AppStateManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class PackageUtil {
|
||||
private static List<String> mEnabled3rdPreloadedApps = Arrays.asList("com.facebook.lite", "com.facebook.lite.stub", "com.facebook.system", "com.facebook.appmanager", "com.whatsapp");
|
||||
private static List<String> mDisabled3rdApps = Arrays.asList("com.borqs.ime", "com.android.cts.verifier", "ademar.textlauncher", "com.mixplorer");
|
||||
|
||||
private static boolean isSystemAppInTop(Context context) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService("activity");
|
||||
ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ApplicationInfo info = pm.getApplicationInfo(cn.getPackageName(), 0);
|
||||
return (info.flags & 1) != 0;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean shouldPointerBeHidden(CharSequence useWindowPackageName, Context context) {
|
||||
String packageName = (String) useWindowPackageName;
|
||||
|
||||
switch (AppStateManager.getState(packageName)) {
|
||||
case AppStateManager.AppState.WHITELISTED:
|
||||
return false;
|
||||
case AppStateManager.AppState.BLACKLISTED:
|
||||
return true;
|
||||
case AppStateManager.AppState.DEFAULT:
|
||||
default:
|
||||
return !mEnabled3rdPreloadedApps.contains(packageName) && (isSystemAppInTop(context) || mDisabled3rdApps.contains(packageName) || context.getPackageName().equals(packageName));
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateEnabled3rdPreloadedApps(Context context) {
|
||||
List<String> apps;
|
||||
String enabled3rdPreloadedApps = Settings.System.getString(context.getContentResolver(), "enabled_3rd_preloaded_apps");
|
||||
if (enabled3rdPreloadedApps != null && !enabled3rdPreloadedApps.equals("") && (apps = Arrays.asList(enabled3rdPreloadedApps.split("\\|"))) != null && apps.size() > 0) {
|
||||
for (String s : apps) {
|
||||
if (!mEnabled3rdPreloadedApps.contains(s)) {
|
||||
mEnabled3rdPreloadedApps.add(s);
|
||||
} else {
|
||||
Log.d("MousePointer", "duplicate s == " + s);
|
||||
}
|
||||
}
|
||||
Log.d("MousePointer", " updateEnabled3rdPreloadedApps mEnabled3rdPreloadedApps :: " + enabled3rdPreloadedApps + " mEnabled3rdPreloadedApps size :: " + mEnabled3rdPreloadedApps.size());
|
||||
}
|
||||
}
|
||||
|
||||
public static float getDensity(Context context) {
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
return dm.density;
|
||||
}
|
||||
|
||||
public static int getStatusBarHeight(Context context) {
|
||||
int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
int statusBarHeight = context.getResources().getDimensionPixelSize(resId);
|
||||
return statusBarHeight;
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package org.eu.octt.supermousepointer.window;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.eu.octt.supermousepointer.AppStateManager;
|
||||
import org.eu.octt.supermousepointer.R;
|
||||
import org.eu.octt.supermousepointer.utils.PackageUtil;
|
||||
|
||||
public class MousePointer {
|
||||
private Context mContext;
|
||||
private int mMaxMousePointerX;
|
||||
private int mMaxMousePointerY;
|
||||
private int mScreenHeight;
|
||||
private int mScreenWidth;
|
||||
private int mStatusBarHeight;
|
||||
private WindowManager mWM;
|
||||
private ImageView mouseImg;
|
||||
private FrameLayout mousePointerLayout;
|
||||
|
||||
private void initMouse() {
|
||||
initMouseRelatedParams();
|
||||
initMousePointerWindow();
|
||||
}
|
||||
|
||||
private void initMousePointerWindow() {
|
||||
this.mWM = (WindowManager) this.mContext.getSystemService("window");
|
||||
this.mousePointerLayout = new FrameLayout(this.mContext);
|
||||
this.mousePointerLayout.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
this.mouseImg = new ImageView(this.mContext);
|
||||
this.mouseImg.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
this.mouseImg.setImageResource(AppStateManager.getPointerDrawableId(mContext, AppStateManager.getSelectedPointerStyle()));
|
||||
this.mousePointerLayout.addView(this.mouseImg);
|
||||
this.mouseImg.setClickable(false);
|
||||
this.mouseImg.setFocusable(false);
|
||||
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
|
||||
params.height = -1;
|
||||
params.width = -1;
|
||||
params.flags = 24;
|
||||
params.format = -3;
|
||||
params.type = 2010;
|
||||
params.gravity = 51;
|
||||
this.mWM.addView(this.mousePointerLayout, params);
|
||||
}
|
||||
|
||||
private void initMouseRelatedParams() {
|
||||
DisplayMetrics dm = this.mContext.getResources().getDisplayMetrics();
|
||||
this.mStatusBarHeight = PackageUtil.getStatusBarHeight(this.mContext);
|
||||
this.mScreenWidth = dm.widthPixels;
|
||||
this.mScreenHeight = dm.heightPixels;
|
||||
this.mMaxMousePointerX = this.mScreenWidth - ((int) (PackageUtil.getDensity(this.mContext) * 10.0f));
|
||||
this.mMaxMousePointerY = (this.mScreenHeight - this.mStatusBarHeight) - ((int) (PackageUtil.getDensity(this.mContext) * 10.0f));
|
||||
Log.d("MousePointer", "initMouseRelatedParams ScreenWidth == " + dm.widthPixels + " ScreenHeight == " + dm.heightPixels + " mMaxMousePointerX == " + this.mMaxMousePointerX + " mMaxMousePointerY == " + this.mMaxMousePointerY + " mStatusBarHeight == " + this.mStatusBarHeight + " density == " + dm.density + " densityDpi == " + dm.densityDpi);
|
||||
}
|
||||
|
||||
private boolean isInMouseMovementRange() {
|
||||
float mousePointerX = this.mouseImg.getTranslationX();
|
||||
float mousePointerY = this.mouseImg.getTranslationY();
|
||||
return mousePointerX <= ((float) this.mMaxMousePointerX) && mousePointerX >= 0.0f && mousePointerY <= ((float) this.mMaxMousePointerY) && mousePointerY >= 0.0f;
|
||||
}
|
||||
|
||||
private void moveMouseX(float relX) {
|
||||
float targetX = this.mouseImg.getTranslationX() + relX;
|
||||
if (targetX > this.mMaxMousePointerX) {
|
||||
this.mouseImg.setTranslationX(this.mMaxMousePointerX);
|
||||
} else if (targetX < 0.0f) {
|
||||
this.mouseImg.setTranslationX(0.0f);
|
||||
} else {
|
||||
this.mouseImg.setTranslationX(targetX);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveMouseY(float relY) {
|
||||
float targetY = this.mouseImg.getTranslationY() + relY;
|
||||
if (targetY > this.mMaxMousePointerY) {
|
||||
this.mouseImg.setTranslationY(this.mMaxMousePointerY);
|
||||
} else if (targetY < 0.0f) {
|
||||
this.mouseImg.setTranslationY(0.0f);
|
||||
} else {
|
||||
this.mouseImg.setTranslationY(targetY);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStatusBarShowing() {
|
||||
int[] top = new int[2];
|
||||
this.mouseImg.getLocationOnScreen(top);
|
||||
return ((float) top[1]) > this.mouseImg.getTranslationY();
|
||||
}
|
||||
|
||||
private boolean needToRefreshMouseMovementRange() {
|
||||
return this.mouseImg.getTranslationY() >= ((float) ((this.mScreenHeight - this.mStatusBarHeight) - ((int) (10.0f * PackageUtil.getDensity(this.mContext)))));
|
||||
}
|
||||
|
||||
private void updateMouseRangeMaxY() {
|
||||
if (needToRefreshMouseMovementRange()) {
|
||||
if (isStatusBarShowing()) {
|
||||
this.mMaxMousePointerY = (this.mScreenHeight - this.mStatusBarHeight) - ((int) (PackageUtil.getDensity(this.mContext) * 10.0f));
|
||||
} else {
|
||||
this.mMaxMousePointerY = this.mScreenHeight - ((int) (PackageUtil.getDensity(this.mContext) * 10.0f));
|
||||
}
|
||||
Log.e("MousePointer", "updateMouseRangeMaxY mMaxMousePointerY = " + this.mMaxMousePointerY);
|
||||
}
|
||||
if (this.mouseImg.getTranslationY() > this.mMaxMousePointerY) {
|
||||
this.mouseImg.setTranslationY(this.mMaxMousePointerY);
|
||||
}
|
||||
}
|
||||
|
||||
public MousePointer(Context context) {
|
||||
this.mContext = context;
|
||||
initMouse();
|
||||
}
|
||||
|
||||
public void showMouse() {
|
||||
this.mousePointerLayout.setVisibility(0);
|
||||
}
|
||||
|
||||
public void hideMouse() {
|
||||
this.mousePointerLayout.setVisibility(8);
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return this.mousePointerLayout.getVisibility() == 0;
|
||||
}
|
||||
|
||||
public void moveMouse(float relX, float relY) {
|
||||
Log.d("MousePointer", "moveMouse == relX == " + relX + " relY == " + relY + " translateX == " + this.mouseImg.getTranslationX() + " translateY == " + this.mouseImg.getTranslationY());
|
||||
updateMouseRangeMaxY();
|
||||
if (isInMouseMovementRange()) {
|
||||
moveMouseX(relX);
|
||||
moveMouseY(relY);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOnMouseMovementRangeBoundary(float offsetX, float offsetY) {
|
||||
return (offsetY > 0.0f && this.mouseImg.getTranslationY() == ((float) this.mMaxMousePointerY)) || (offsetY < 0.0f && this.mouseImg.getTranslationY() == 0.0f) || ((offsetX > 0.0f && this.mouseImg.getTranslationX() == ((float) this.mMaxMousePointerX)) || (offsetX < 0.0f && this.mouseImg.getTranslationX() == 0.0f));
|
||||
}
|
||||
|
||||
public float getMousePointerX() {
|
||||
return this.mouseImg.getTranslationX();
|
||||
}
|
||||
|
||||
public float getMousePointerY() {
|
||||
int[] top = new int[2];
|
||||
this.mouseImg.getLocationOnScreen(top);
|
||||
return top[1];
|
||||
}
|
||||
|
||||
public void setImageResource(int newDrawableId) {
|
||||
this.mouseImg.setImageResource(newDrawableId);
|
||||
}
|
||||
}
|
BIN
app/src/main/res/drawable-hdpi/pointer_arrow.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/drawable-hdpi/pointer_arrow_app.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-hdpi/pointer_arrow_inverted.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
11
app/src/main/res/drawable/ic_state_blacklist.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- Red X in circle -->
|
||||
<path
|
||||
android:fillColor="#F44336"
|
||||
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10-4.47 10-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
|
||||
</vector>
|
13
app/src/main/res/drawable/ic_state_default.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- Empty circle outline -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#808080"
|
||||
android:strokeWidth="2"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10-4.48 10-10S17.52,2 12,2z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_state_whitelist.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- Green checkmark in circle -->
|
||||
<path
|
||||
android:fillColor="#4CAF50"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10-4.48 10-10S17.52,2 12,2zM10,17l-5-5 1.41-1.41L10,14.17l7.59-7.59L19,8l-9,9z"/>
|
||||
</vector>
|
39
app/src/main/res/layout/activity_settings.xml
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Tabs -->
|
||||
<RadioGroup
|
||||
android:id="@+id/tabGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/tabApps"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/tab_apps"
|
||||
style="@style/TabButton"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/tabCursor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/tab_cursor"
|
||||
style="@style/TabButton"/>
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
<!-- Content -->
|
||||
<FrameLayout
|
||||
android:id="@+id/contentFrame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
50
app/src/main/res/layout/dialog_about.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/appIcon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appVersion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/links"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:autoLink="web"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
</LinearLayout>
|
10
app/src/main/res/layout/fragment_app_list.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ListView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/appList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
android:choiceMode="singleChoice"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"/>
|
12
app/src/main/res/layout/fragment_cursor_style.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ListView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/cursorStyleList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@android:color/darker_gray"
|
||||
android:dividerHeight="1dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"/>
|
29
app/src/main/res/layout/list_item_app.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/state"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:focusable="false"/>
|
||||
</LinearLayout>
|
33
app/src/main/res/layout/list_item_cursor_style.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:focusable="false"
|
||||
android:clickable="false"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pointerImage"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/pointer_arrow"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pointerName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textSize="16sp"/>
|
||||
</LinearLayout>
|
8
app/src/main/res/menu/settings_menu.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/menu_info"
|
||||
android:title="@string/info"
|
||||
android:icon="@android:drawable/ic_menu_info_details"
|
||||
android:showAsAction="always"/>
|
||||
</menu>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 488 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 664 B |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 817 B |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
3
app/src/main/res/values-hdpi/drawables.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
7
app/src/main/res/values-it/strings.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="settings_title" translatable="false">SuperMousePointer (Impostazioni)</string>
|
||||
<string name="accessibility_description">Un mouse virtuale per Android legacy che può essere usato nelle app desiderate con le freccette di navigazione.</string>
|
||||
<string name="tab_apps">App</string>
|
||||
<string name="tab_cursor">Cursore</string>
|
||||
</resources>
|
10
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
9
app/src/main/res/values/public.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<public type="drawable" name="pointer_arrow" id="0x7f020000" />
|
||||
<public type="layout" name="mouse_pointer_layout" id="0x7f030000" />
|
||||
<public type="xml" name="keyevent_service_config" id="0x7f040000" />
|
||||
<public type="string" name="app_name" id="0x7f050000" />
|
||||
<public type="string" name="accessibility_description" id="0x7f050001" />
|
||||
<public type="id" name="mousePointer" id="0x7f060000" />
|
||||
</resources>
|
12
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">SuperMousePointer</string>
|
||||
<string name="settings_title" translatable="false">SuperMousePointer (Settings)</string>
|
||||
<string name="accessibility_description">A virtual mouse for legacy Android which can be used in desired apps via the arrow navigation keys.</string>
|
||||
<string name="info" translatable="false">Info</string>
|
||||
<string name="state_default" translatable="false">Default</string>
|
||||
<string name="state_whitelisted" translatable="false">Whitelist</string>
|
||||
<string name="state_blacklisted" translatable="false">Blacklist</string>
|
||||
<string name="tab_apps">Apps</string>
|
||||
<string name="tab_cursor">Cursor</string>
|
||||
</resources>
|
9
app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="TabButton" parent="android:Widget.CompoundButton.RadioButton">
|
||||
<item name="android:button">@null</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<!-- <item name="android:background">@drawable/tab_selector</item>-->
|
||||
<!-- <item name="android:textColor">@drawable/tab_text_selector</item>-->
|
||||
</style>
|
||||
</resources>
|
13
app/src/main/res/xml/backup_rules.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
13
app/src/main/res/xml/keyevent_service_config.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:description="@string/accessibility_description"
|
||||
android:accessibilityEventTypes="typeAllMask"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:notificationTimeout="100"
|
||||
android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagRequestFilterKeyEvents"
|
||||
android:canRequestTouchExplorationMode="true"
|
||||
android:canRequestEnhancedWebAccessibility="true"
|
||||
android:canPerformGestures="true"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:canRequestFilterKeyEvents="true"
|
||||
android:settingsActivity=".SettingsActivity"/>
|
4
build.gradle
Normal file
@ -0,0 +1,4 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
}
|
21
gradle.properties
Normal file
@ -0,0 +1,21 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
18
gradle/libs.versions.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[versions]
|
||||
agp = "8.7.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
appcompat = "1.7.0"
|
||||
material = "1.12.0"
|
||||
|
||||
[libraries]
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Fri Mar 14 08:35:51 CET 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
185
gradlew
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
23
settings.gradle
Normal file
@ -0,0 +1,23 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("com\\.android.*")
|
||||
includeGroupByRegex("com\\.google.*")
|
||||
includeGroupByRegex("androidx.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "SuperMousePointer"
|
||||
include ':app'
|