This commit is contained in:
2025-03-16 01:29:34 +01:00
commit 850bdf90c8
58 changed files with 2105 additions and 0 deletions

15
.gitignore vendored Normal file
View 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
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml generated Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
# SuperMousePointer ![](app/src/main/res/mipmap-mdpi/ic_launcher.png)
A virtual mouse for legacy Android which can be used in desired apps via the arrow navigation keys.

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

41
app/build.gradle Normal file
View 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
View 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

View 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>

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
// });
// }
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View 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>

View 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>

View 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>

View 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>

View 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>

View 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"/>

View 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"/>

View 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>

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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
View 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
View 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

Binary file not shown.

View 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
View 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
View 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
View 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'