Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions .github/workflows/build_apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Build_TouchHelper_APK
# events but only for the master branch
on:
push:
branches: [ master ]
branches: [ master, refactor ]
paths-ignore:
- .github/workflows
- 'docs/**'
Expand Down Expand Up @@ -41,27 +41,37 @@ jobs:
distribution: 'temurin'
java-version: '17'

- name: Assemble Release APK
run: ./gradlew assembleRelease --stacktrace
- name: Assemble Release APK and AAB
run: ./gradlew assembleRelease bundleRelease --stacktrace

- name: Upload APK to artifacts
uses: actions/upload-artifact@v4
with:
name: TouchHelper
name: TouchHelper-APK
path: ./app/build/outputs/apk/

- name: Upload AAB to artifacts
uses: actions/upload-artifact@v4
with:
name: TouchHelper-AAB
path: ./app/build/outputs/bundle/release/

- name: Create ZIPs
working-directory: ./app/build/outputs/apk/
if: github.ref == 'refs/heads/master'
working-directory: ./app/build/outputs/
run: |
zip -r ./TouchHelper.zip ./release
zip -r ./TouchHelper-APK.zip ./apk/release
zip -r ./TouchHelper-AAB.zip ./bundle/release

- name: Get git revision
if: github.ref == 'refs/heads/master'
id: get_git_revision
run: |
echo "tag_name=$(($(git rev-list HEAD --count) + 100))" >> $GITHUB_OUTPUT
echo "release_name=$(($(git rev-list HEAD --count) + 100))" >> $GITHUB_OUTPUT

- name: Create Release and Upload APK
if: github.ref == 'refs/heads/master'
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -70,4 +80,6 @@ jobs:
name: Release ${{ steps.get_git_revision.outputs.release_name }}
draft: false
prerelease: false
files: ./app/build/outputs/apk/TouchHelper.zip
files: |
./app/build/outputs/TouchHelper-APK.zip
./app/build/outputs/TouchHelper-AAB.zip
24 changes: 12 additions & 12 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,21 @@ android {
}

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
implementation 'androidx.navigation:navigation-fragment:2.1.0'
implementation 'androidx.navigation:navigation-ui:2.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.navigation:navigation-fragment:2.8.5'
implementation 'androidx.navigation:navigation-ui:2.8.5'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.8.7'
implementation 'androidx.lifecycle:lifecycle-livedata:2.8.7'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'

implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.google.code.gson:gson:2.11.0'

// https://developer.android.com/jetpack/androidx/releases/preference?hl=zh-cn
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.preference:preference:1.2.1'

// https://mvnrepository.com/artifact/com.belerweb/pinyin4j
implementation group: 'com.belerweb', name: 'pinyin4j', version: '2.5.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class TouchHelperServiceImpl {
private static final int PACKAGE_POSITION_CLICK_RETRY_INTERVAL = 500;
private static final int PACKAGE_POSITION_CLICK_RETRY = 6;
private boolean isShow = false;
private long lastContentChangedTime = 0;

public TouchHelperServiceImpl(AccessibilityService service) {
this.service = service;
Expand Down Expand Up @@ -361,6 +362,12 @@ public void run() {
break;
}

long currentTime = System.currentTimeMillis();
if (currentTime - lastContentChangedTime < 150) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The value 150 is a magic number. It's better to define it as a named constant to improve readability and maintainability, for example: private static final int CONTENT_CHANGED_THROTTLE_INTERVAL_MS = 150; at the class level.

break;
}
lastContentChangedTime = currentTime;

Comment on lines +365 to +370
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify timer APIs used for debounce/interval checks in the repo.
# Expectation: elapsedRealtime()/uptimeMillis() for elapsed checks, avoid currentTimeMillis() for throttling.
rg -n -C2 'currentTimeMillis\(|elapsedRealtime\(|uptimeMillis\('

Repository: zfdang/Android-Touch-Helper

Length of output: 626


🏁 Script executed:

# Read the full context around line 365 and nearby code
sed -n '350,380p' app/src/main/java/com/zfdang/touchhelper/TouchHelperServiceImpl.java

Repository: zfdang/Android-Touch-Helper

Length of output: 1549


🏁 Script executed:

# Find where lastContentChangedTime is declared and initialized
rg -n 'lastContentChangedTime' app/src/main/java/com/zfdang/touchhelper/TouchHelperServiceImpl.java

Repository: zfdang/Android-Touch-Helper

Length of output: 254


🏁 Script executed:

# Check current imports in the file
head -30 app/src/main/java/com/zfdang/touchhelper/TouchHelperServiceImpl.java

Repository: zfdang/Android-Touch-Helper

Length of output: 1155


Use a monotonic clock for debounce timing.

Line 365 uses System.currentTimeMillis(), which returns wall-clock time that can jump due to system clock adjustments (NTP sync, manual adjustments, etc.), breaking debounce logic. Use SystemClock.elapsedRealtime() for elapsed interval measurements.

Proposed fix
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemClock;
@@
-                    long currentTime = System.currentTimeMillis();
+                    long currentTime = SystemClock.elapsedRealtime();
                     if (currentTime - lastContentChangedTime < 150) {
                         break;
                     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
long currentTime = System.currentTimeMillis();
if (currentTime - lastContentChangedTime < 150) {
break;
}
lastContentChangedTime = currentTime;
long currentTime = SystemClock.elapsedRealtime();
if (currentTime - lastContentChangedTime < 150) {
break;
}
lastContentChangedTime = currentTime;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/zfdang/touchhelper/TouchHelperServiceImpl.java` around
lines 365 - 370, The debounce uses wall-clock time via
System.currentTimeMillis(), which can jump; replace that call with a monotonic
clock (SystemClock.elapsedRealtime()) for computing the delta against
lastContentChangedTime in TouchHelperServiceImpl (the variable
lastContentChangedTime and the block that sets it). Update the reference so
lastContentChangedTime stores elapsedRealtime() values and ensure
android.os.SystemClock is imported; keep the comparison logic the same (compare
elapsedRealtime() - lastContentChangedTime < 150) and then set
lastContentChangedTime = SystemClock.elapsedRealtime().

if (setTargetedWidgets != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "method by widget in CONTENT_CHANGED");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;

import com.zfdang.touchhelper.R;

public class AboutFragment extends Fragment {
Expand Down
17 changes: 12 additions & 5 deletions app/src/main/java/com/zfdang/touchhelper/ui/home/HomeFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.lifecycle.ViewModelProvider;

import com.zfdang.touchhelper.R;
import com.zfdang.touchhelper.TouchHelperService;
Expand All @@ -45,7 +45,7 @@ public class HomeFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
homeViewModel =
ViewModelProviders.of(this).get(HomeViewModel.class);
new ViewModelProvider(this).get(HomeViewModel.class);
View root = inflater.inflate(R.layout.fragment_home, container, false);

final Drawable drawableYes = ContextCompat.getDrawable(getContext(), R.drawable.ic_right);
Expand Down Expand Up @@ -82,9 +82,16 @@ public void onChanged(Boolean aBoolean) {
btAccessibilityPermission.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent_abs = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent_abs.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent_abs);
new androidx.appcompat.app.AlertDialog.Builder(getContext())
.setTitle("重要说明 (Prominent Disclosure)")
.setMessage("本应用需要使用无障碍服务(AccessibilityService API)来帮您在其他应用中自动查找并点击屏幕上的指定控件(例如各类重复性的按钮)。\n\n本应用属于自动化工具,所有的点击操作均基于您的配置和触发策略。\n本应用完全离线运行,不会收集、存储、传输或分享您的任何个人数据及敏感信息。\n\n请点击“同意”以继续前往系统设置中授权此服务。")
.setPositiveButton("同意", (dialog, which) -> {
Intent intent_abs = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent_abs.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent_abs);
})
.setNegativeButton("拒绝", null)
.show();
Comment on lines +85 to +94
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The title, message, and button texts for this AlertDialog are hardcoded. It's a best practice to extract these strings into strings.xml resources. This improves maintainability and makes it easier to support multiple languages in the future.

}
});

Expand Down
15 changes: 15 additions & 0 deletions app/src/main/res/layout/fragment_about.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_app_introduction" />

<TextView
android:id="@+id/textView_privacy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:autoLink="web"
android:linksClickable="true"
android:paddingLeft="12dp"
android:paddingRight="12dp"
Comment on lines +75 to +76
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all directional-padding opportunities in XML layouts.
rg -n --type=xml -C2 'paddingLeft|paddingRight'

Repository: zfdang/Android-Touch-Helper

Length of output: 2830


🏁 Script executed:

# Check the project's minSdkVersion
find . -name "AndroidManifest.xml" -o -name "build.gradle" -o -name "build.gradle.kts" | head -5

Repository: zfdang/Android-Touch-Helper

Length of output: 139


🏁 Script executed:

# Also check for minSdkVersion in gradle files
rg -n "minSdkVersion" --type=gradle

Repository: zfdang/Android-Touch-Helper

Length of output: 115


Use directional padding attributes for RTL-safe layouts.

Lines 75-76 should use android:paddingStart and android:paddingEnd instead of paddingLeft and paddingRight. The project's minSdkVersion is 26, which fully supports directional padding (available since API 17).

♻️ Proposed update
-        android:paddingLeft="12dp"
-        android:paddingRight="12dp"
+        android:paddingStart="12dp"
+        android:paddingEnd="12dp"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/layout/fragment_about.xml` around lines 75 - 76, Replace the
legacy RTL-unsafe attributes android:paddingLeft and android:paddingRight in the
fragment_about layout with directional attributes android:paddingStart and
android:paddingEnd (remove the left/right attributes) so the layout is RTL-safe;
update the attributes where they appear in fragment_about.xml (lines showing
android:paddingLeft and android:paddingRight) to android:paddingStart and
android:paddingEnd respectively, leveraging the project's minSdkVersion >= 26
which supports directional padding.

android:text="@string/about_privacy"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_weblink" />
Comment on lines +68 to +81
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) Confirm this layout is not already wrapped by a scroll container via include/inflate usage context.
rg -n -C3 'fragment_about|R\.layout\.fragment_about|inflate\([^)]*fragment_about'

# 2) List long text blocks in this layout that increase vertical pressure.
rg -n --type=xml -C1 'textView_app_introduction|textView_weblink|textView_privacy|layout_constraintBottom_toBottomOf'

Repository: zfdang/Android-Touch-Helper

Length of output: 5080


🏁 Script executed:

cat -n app/src/main/res/layout/fragment_about.xml

Repository: zfdang/Android-Touch-Helper

Length of output: 4438


🏁 Script executed:

rg -n -B5 -A5 'FragmentContainerView|fragment_container|R\.id\..*container' app/src/main/res/layout/activity_main.xml

Repository: zfdang/Android-Touch-Helper

Length of output: 53


🏁 Script executed:

cat -n app/src/main/res/layout/activity_main.xml

Repository: zfdang/Android-Touch-Helper

Length of output: 1788


Wrap content in ScrollView or NestedScrollView to prevent overflow.

The fragment's root ConstraintLayout (and parent NavHostFragment) are non-scrollable. Multiple stacked TextViews with match_parent width—especially weblink and privacy fields with wrappable URLs—can overflow on small screens or with large font sizes, causing content clipping below the bottom-anchored copyright text. Consider wrapping the content above the copyright view in a NestedScrollView to ensure all text remains accessible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/layout/fragment_about.xml` around lines 68 - 81, The
fragment can overflow on small screens because the stacked TextViews (e.g.,
`@id/textView_weblink` and `@id/textView_privacy`) live directly in the
non-scrollable ConstraintLayout; wrap the scrollable portion in a
NestedScrollView: create a NestedScrollView (or ScrollView) inside the root
ConstraintLayout, move the TextViews that should scroll (weblink, privacy, any
other descriptive TextViews) into a single vertical container (LinearLayout or
ConstraintLayout) inside that NestedScrollView, set those child views to
wrap_content height, and constrain the NestedScrollView between the top of the
parent and the top of the bottom copyright view (use
app:layout_constraintBottom_toTopOf="@id/textView_copyright"); preserve the
existing IDs and link attributes (autoLink, linksClickable) and ensure the
NestedScrollView width is match_parent so long text can wrap and scroll instead
of being clipped.


<TextView
android:id="@+id/textView_copyright"
android:layout_width="wrap_content"
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</string>

<string name="about_weblink">https://touchhelper.zfdang.com/</string>
<string name="about_privacy">隐私政策 (Privacy Policy):\nhttps://touchhelper.zfdang.com/privacy.html</string>
<string name="about_copyright">Copyright © 2022 Zhengfa Dang</string>

<string name="app_version">版本: %s (%d)</string>
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.2'
classpath 'com.android.tools.build:gradle:8.7.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
7 changes: 4 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ org.gradle.jvmargs=-Xmx2048m
# 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
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Jetifier no longer needed - all dependencies use AndroidX natively
# android.enableJetifier=true

# Keystore credentials (DO NOT commit real values to version control)
# Set these in local.properties or as environment variables
KEYSTORE_PASSWORD=touchhelper
KEY_ALIAS=touchhelper
KEY_PASSWORD=touchhelper
KEY_PASSWORD=touchhelper
android.suppressUnsupportedCompileSdk=35
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
Loading