Skip to content
Merged
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
10 changes: 5 additions & 5 deletions .github/workflows/build_apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

# Steps represent a sequence of tasks that will be executed as part of the job
steps:

# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: checkout codes
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: '0' # 0 indicates all history, this is needed for git revision

- name: Set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '17'
Expand All @@ -48,7 +48,7 @@ jobs:
run: ./gradlew assembleRelease --stacktrace

- name: Upload APK to artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: TouchHelper
path: ./app/build/outputs/apk/
Expand All @@ -65,7 +65,7 @@ jobs:
echo "release_name=$(($(git rev-list HEAD --count) + 100))" >> $GITHUB_OUTPUT

- name: Create Release and Upload APK
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
Expand Down
28 changes: 16 additions & 12 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def appVersion = getDate()

android {
namespace "com.zfdang.touchhelper"
compileSdkVersion 35
compileSdkVersion 36

buildFeatures {
buildConfig true
Expand All @@ -23,7 +23,7 @@ android {
defaultConfig {
applicationId "com.zfdang.touchhelper"
minSdkVersion 26
targetSdkVersion 35
targetSdkVersion 36
versionCode gitVersion
versionName appVersion

Expand Down Expand Up @@ -81,21 +81,25 @@ 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'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.activity:activity:1.9.3'
implementation 'androidx.fragment:fragment:1.8.5'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation 'androidx.navigation:navigation-fragment:2.8.5'
implementation 'androidx.navigation:navigation-ui:2.8.5'
// lifecycle-extensions is discontinued; ViewModel / LiveData ship as separate artifacts
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.8.7'
implementation 'androidx.lifecycle:lifecycle-livedata:2.8.7'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.14.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
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
23 changes: 16 additions & 7 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

<!--
Package visibility (API 30+): the service needs the list of launchable apps to build its
working list / whitelist, and the list of input methods to ignore their windows. Both are
resolved through these queries, so QUERY_ALL_PACKAGES is not needed.
Overlays are TYPE_ACCESSIBILITY_OVERLAY windows of the accessibility service, which need
no SYSTEM_ALERT_WINDOW; the battery page is opened via
ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS, which needs no permission either.
-->
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent>
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
</intent>
<intent>
<action android:name="android.view.InputMethod" />
</intent>
</queries>

Expand All @@ -20,7 +30,6 @@
android:label="@string/app_name"
android:roundIcon="@drawable/ic_touch_helper_icon"
android:supportsRtl="true"
android:persistent="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/zfdang/touchhelper/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.google.android.material.bottomnavigation.BottomNavigationView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
Expand All @@ -16,6 +17,9 @@ public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// the window has no ActionBar (edge-to-edge friendly); this toolbar acts as one
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
BottomNavigationView navView = findViewById(R.id.nav_view);

// Passing each menu ID as a set of Ids because each
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/java/com/zfdang/touchhelper/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ private void initSettings() {

// init all settings from SharedPreferences
bSkipAdNotification = mPreference.getBoolean(SKIP_AD_NOTIFICATION, true);
bDisclosureAccepted = mPreference.getBoolean(ACCESSIBILITY_DISCLOSURE_ACCEPTED, false);

// initial duration of skip ad process
iSkipAdDuration = mPreference.getInt(SKIP_AD_DURATION, 4);
Expand Down Expand Up @@ -108,6 +109,21 @@ private static Set<String> querySystemLauncherPackages() {
return pkgSystems;
}

// has the user confirmed the in-app disclosure about what the accessibility service reads?
// Google Play requires this consent before the service may process screen content, so the
// service stays idle until it is recorded (also for users who enabled the service before
// the disclosure existed).
private static final String ACCESSIBILITY_DISCLOSURE_ACCEPTED = "ACCESSIBILITY_DISCLOSURE_ACCEPTED";
private volatile boolean bDisclosureAccepted;
public boolean isDisclosureAccepted() {
return bDisclosureAccepted;
}
public void setDisclosureAccepted(boolean accepted) {
bDisclosureAccepted = accepted;
mEditor.putBoolean(ACCESSIBILITY_DISCLOSURE_ACCEPTED, accepted);
mEditor.apply();
}

// notification on skip ads?
private static final String SKIP_AD_NOTIFICATION = "SKIP_AD_NOTIFICATION";
private boolean bSkipAdNotification;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ private void InstallReceiverAndHandler() {
// 1. TYPE_WINDOW_STATE_CHANGED, 判断packageName和activityName,决定是否开始跳过检测; 然后使用三种方法去尝试跳过
// 2. TYPE_WINDOW_CONTENT_CHANGED, 使用两种方法去尝试跳过
public void onAccessibilityEvent(AccessibilityEvent event) {
if (!mSetting.isDisclosureAccepted()) {
// no screen content is read or clicked until the user has confirmed the in-app
// disclosure; tell them once per process where to do that
remindDisclosureOnce();
return;
}
if (BuildConfig.DEBUG) {
Log.d(TAG, AccessibilityEvent.eventTypeToString(event.getEventType()) + " - " + event.getPackageName() + " - " + event.getClassName() + "; ");
Log.d(TAG, " currentPackageName = " + currentPackageName + " currentActivityName = " + currentActivityName);
Expand Down Expand Up @@ -501,6 +507,14 @@ public void run() {
}
}

private boolean disclosureReminderShown;

private void remindDisclosureOnce() {
if (disclosureReminderShown || receiverHandler == null) return;
disclosureReminderShown = true;
receiverHandler.post(() -> Toast.makeText(service, R.string.accessibility_disclosure_reminder, Toast.LENGTH_LONG).show());
}

public void onUnbind(Intent intent) {
try {
stopSkipAdProcessInner();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.pm.PackageInfoCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;

import com.zfdang.touchhelper.R;

Expand All @@ -28,12 +27,12 @@ public View onCreateView(@NonNull LayoutInflater inflater,
TextView tvVersion = root.findViewById(R.id.textView_version);

String versionName = "unknown";
int versionCode = 0;
long versionCode = 0;
PackageManager pm = getActivity().getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo(getActivity().getPackageName(), 0);
versionName = pi.versionName;
versionCode = pi.versionCode;
versionCode = PackageInfoCompat.getLongVersionCode(pi);
// generate about_content with version from manifest
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "showInfoDialog: " + Log.getStackTraceString(e));
Expand Down
80 changes: 53 additions & 27 deletions app/src/main/java/com/zfdang/touchhelper/ui/home/HomeFragment.java
Original file line number Diff line number Diff line change
@@ -1,37 +1,28 @@
package com.zfdang.touchhelper.ui.home;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.text.method.Touch;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.LongDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.Fragment;
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 @@ -44,8 +35,7 @@ public class HomeFragment extends Fragment {

public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
homeViewModel =
ViewModelProviders.of(this).get(HomeViewModel.class);
homeViewModel = 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 @@ -79,14 +69,7 @@ public void onChanged(Boolean aBoolean) {

// set listener for buttons
final ImageButton btAccessibilityPermission = root.findViewById(R.id.button_accessibility_permission);
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);
}
});
btAccessibilityPermission.setOnClickListener(v -> showAccessibilityDisclosure());

final ImageButton btPowerPermission = root.findViewById(R.id.button_power_permission);
btPowerPermission.setOnClickListener(new View.OnClickListener() {
Expand Down Expand Up @@ -116,16 +99,59 @@ public void onClick(View v) {
public void onResume() {
checkServiceStatus();
super.onResume();
// the service was enabled from the system settings (or before the disclosure existed)
// but the user has not confirmed the disclosure yet: it stays idle until they do
if (TouchHelperService.isServiceRunning()
&& !com.zfdang.touchhelper.Settings.getInstance().isDisclosureAccepted()) {
showAccessibilityDisclosure();
}
}

public void checkServiceStatus(){
@Override
public void onPause() {
if (disclosureDialog != null && disclosureDialog.isShowing()) {
disclosureDialog.dismiss();
}
super.onPause();
}

/**
* Google Play requires a prominent in-app disclosure before an app sends the user to enable
* an accessibility service that is not an assistive tool: what the service reads, what it
* does with it, and that nothing leaves the device.
*/
private void showAccessibilityDisclosure() {
if (com.zfdang.touchhelper.Settings.getInstance().isDisclosureAccepted()) {
openAccessibilitySettings();
return;
}
if (disclosureDialog != null && disclosureDialog.isShowing()) {
return;
}
final boolean serviceRunning = TouchHelperService.isServiceRunning();
disclosureDialog = new AlertDialog.Builder(requireContext())
.setTitle(R.string.accessibility_disclosure_title)
.setMessage(R.string.accessibility_disclosure_message)
.setPositiveButton(serviceRunning ? R.string.accessibility_disclosure_agree_only : R.string.accessibility_disclosure_agree,
(dialog, which) -> {
com.zfdang.touchhelper.Settings.getInstance().setDisclosureAccepted(true);
if (!serviceRunning) {
openAccessibilitySettings();
}
})
.setNegativeButton(R.string.accessibility_disclosure_cancel, null)
.show();
}

// detect the app storage permission
boolean bAppPermission =
ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
MutableLiveData<Boolean> liveData = homeViewModel.getAppPermission();
liveData.setValue(bAppPermission);
private AlertDialog disclosureDialog;

private void openAccessibilitySettings() {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

public void checkServiceStatus(){
// detect the accessibility permission
MutableLiveData<Boolean> accessibility = homeViewModel.getAccessibilityPermission();
accessibility.setValue(TouchHelperService.isServiceRunning());
Expand Down
Loading