diff --git a/app/build.gradle b/app/build.gradle index 9a2f4e47..4faaac81 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,16 +104,16 @@ dependencies { implementation "dev.rikka.shizuku:api:${shizukuVersion}" implementation "dev.rikka.shizuku:provider:${shizukuVersion}" - def appCenterSdkVersion = '5.0.1' + def appCenterSdkVersion = '5.0.2' appcenterImplementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}" appcenterImplementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}" appcenterImplementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}" - implementation 'com.github.Dimezis:BlurView:version-2.0.3' - implementation 'com.github.getActivity:XXPermissions:16.8' - implementation 'com.github.getActivity:XToast:8.9' + implementation 'io.github.iamr0s:Dhizuku-API:2.4' + implementation 'com.github.getActivity:XXPermissions:18.2' + implementation 'com.github.getActivity:EasyWindow:10.2' implementation 'com.github.heruoxin.Delegated-Scopes-Manager:client:master-SNAPSHOT' - implementation 'com.google.android.material:material:1.7.0' + implementation 'com.google.android.material:material:1.9.0' implementation "androidx.biometric:biometric:1.1.0" implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b7dc9dc5..88fabdfd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,19 +51,24 @@ + android:exported="false" /> + android:exported="false" /> + android:exported="false" /> + android:exported="false" /> + android:exported="false" /> + pkgNames) { + setDelegatedScopes(); + for (var pkgName : pkgNames) { + devicePolicyManager.setApplicationHidden(null, pkgName, true); + } + } + + @Override + public void unhide(Set pkgNames) { + setDelegatedScopes(); + for (var pkgName : pkgNames) { + devicePolicyManager.setApplicationHidden(null, pkgName, false); + } + } + + @Override + public CheckAvailabilityResult checkAvailability() { + if (!Dhizuku.init()) { + Log.w("DhizukuHider", "Dhizuku not init."); + return new CheckAvailabilityResult(CheckAvailabilityResult.Result.UNAVAILABLE, R.string.dhizuku_not_init); + } + + if (Dhizuku.getVersionCode() < 5) { + Log.w("DhizukuHider", "Unsupported Dhizuku version: pre v5.x"); + return new CheckAvailabilityResult(CheckAvailabilityResult.Result.UNAVAILABLE, R.string.dhizuku_pre_v5); + } + + return Dhizuku.isPermissionGranted() + ? new CheckAvailabilityResult(CheckAvailabilityResult.Result.AVAILABLE) + : new CheckAvailabilityResult(CheckAvailabilityResult.Result.REQ_PERM); + } + + @Override + public void active(OnActivateCallbackListener onActivateCallbackListener) { + CheckAvailabilityResult r = checkAvailability(); + switch (r.result) { + case UNAVAILABLE -> + onActivateCallbackListener.onActivateCallback(this.getClass(), false, r.msgResID); + case AVAILABLE -> + onActivateCallbackListener.onActivateCallback(this.getClass(), true, 0); + case REQ_PERM -> Dhizuku.requestPermission(new DhizukuRequestPermissionListener() { + @Override + public void onRequestPermission(int grantResult) { + if (grantResult == PackageManager.PERMISSION_GRANTED) { + Log.d("DhizukuHider", "Permission granted."); + onActivateCallbackListener.onActivateCallback(DhizukuHider.class, true, 0); + } else { + Log.d("DhizukuHider", "Permission denied."); + onActivateCallbackListener.onActivateCallback(DhizukuHider.class, false, R.string.dhizuku_permission_denied); + } + } + }); + } + } + + @Override + public String getName() { + return "Dhizuku"; + } + + private void setDelegatedScopes() { + if (Arrays.asList(Dhizuku.getDelegatedScopes()) + .contains(DELEGATION_PACKAGE_ACCESS)) + return; + Dhizuku.setDelegatedScopes(new String[]{DELEGATION_PACKAGE_ACCESS}); + } +} diff --git a/app/src/main/java/deltazero/amarok/PrefMgr.java b/app/src/main/java/deltazero/amarok/PrefMgr.java index 77bf8d23..d7485fac 100644 --- a/app/src/main/java/deltazero/amarok/PrefMgr.java +++ b/app/src/main/java/deltazero/amarok/PrefMgr.java @@ -11,6 +11,7 @@ import java.util.Set; import deltazero.amarok.AppHider.AppHiderBase; +import deltazero.amarok.AppHider.DhizukuHider; import deltazero.amarok.AppHider.DsmAppHider; import deltazero.amarok.AppHider.NoneAppHider; import deltazero.amarok.AppHider.RootAppHider; @@ -58,26 +59,30 @@ public void setHideApps(Set pkgNames) { } public AppHiderBase getAppHider() { - switch (mPrefs.getInt("appHiderMode", 0)) { - case 1: - return new RootAppHider(context); - case 2: - return new DsmAppHider(context); - case 3: - return new ShizukuHider(context); - default: - return new NoneAppHider(context); - } + return switch (mPrefs.getInt("appHiderMode", 0)) { + case 0 -> new NoneAppHider(context); + case 1 -> new RootAppHider(context); + case 2 -> new DsmAppHider(context); + case 3 -> new ShizukuHider(context); + case 4 -> new DhizukuHider(context); + default -> throw new IndexOutOfBoundsException(); + }; } public void setAppHiderMode(Class mode) { - int modeCode = 0; - if (mode == RootAppHider.class) + int modeCode; + if (mode == NoneAppHider.class) + modeCode = 0; + else if (mode == RootAppHider.class) modeCode = 1; - if (mode == DsmAppHider.class) + else if (mode == DsmAppHider.class) modeCode = 2; - if (mode == ShizukuHider.class) + else if (mode == ShizukuHider.class) modeCode = 3; + else if (mode == DhizukuHider.class) + modeCode = 4; + else + throw new IndexOutOfBoundsException(); mPrefEditor.putInt("appHiderMode", modeCode); mPrefEditor.apply(); } diff --git a/app/src/main/java/deltazero/amarok/QuickHideService.java b/app/src/main/java/deltazero/amarok/QuickHideService.java index dbb897cd..7f9a5a40 100644 --- a/app/src/main/java/deltazero/amarok/QuickHideService.java +++ b/app/src/main/java/deltazero/amarok/QuickHideService.java @@ -17,15 +17,15 @@ import com.hjq.permissions.Permission; import com.hjq.permissions.XXPermissions; -import com.hjq.xtoast.XToast; -import com.hjq.xtoast.draggable.SpringDraggable; +import com.hjq.window.EasyWindow; +import com.hjq.window.draggable.SpringDraggable; import deltazero.amarok.ui.MainActivity; public class QuickHideService extends LifecycleService { private MutableLiveData isProcessing; - private XToast panicButton; + private EasyWindow panicButton; private Hider hider; private ImageView ivPanicButton; @@ -87,13 +87,13 @@ public int onStartCommand(Intent intent, int flags, int startId) { isServiceRunning = true; // Init panic button - panicButton = new XToast<>(getApplication()) + panicButton = new EasyWindow<>(getApplication()) .setContentView(R.layout.dialog_panic_button) .setGravity(Gravity.END | Gravity.BOTTOM) .setYOffset(300) .setDraggable(new SpringDraggable()) .setOnClickListener(R.id.dialog_iv_panic_button, - (XToast.OnClickListener) (xToast, view) -> hider.hide()); + (EasyWindow.OnClickListener) (xToast, view) -> hider.hide()); ivPanicButton = panicButton.findViewById(R.id.dialog_iv_panic_button); ivPanicButton.setColorFilter(getColor(R.color.light_grey), diff --git a/app/src/main/java/deltazero/amarok/QuickSettingService.java b/app/src/main/java/deltazero/amarok/QuickSettingService.java index 5e391527..cb53c1f9 100644 --- a/app/src/main/java/deltazero/amarok/QuickSettingService.java +++ b/app/src/main/java/deltazero/amarok/QuickSettingService.java @@ -16,6 +16,8 @@ import androidx.lifecycle.Observer; import androidx.lifecycle.ServiceLifecycleDispatcher; +import deltazero.amarok.ui.SecurityAuthForQuickHideActivity; + public class QuickSettingService extends TileService implements LifecycleOwner { private static final String TAG = "TileService"; @@ -113,7 +115,13 @@ public void onClick() { unlockAndRun(() -> { Log.i(TAG, "Toggled tile."); if (prefMgr.getIsHidden()) { - hider.unhide(); + if (prefMgr.getAmarokPassword() == null) { + // Avoid jump back to Amarok even without password + hider.unhide(); + } else { + startActivityAndCollapse(new Intent(this, SecurityAuthForQuickHideActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } } else { hider.hide(); } diff --git a/app/src/main/java/deltazero/amarok/ui/SecurityAuthForQuickHideActivity.java b/app/src/main/java/deltazero/amarok/ui/SecurityAuthForQuickHideActivity.java new file mode 100644 index 00000000..5694184d --- /dev/null +++ b/app/src/main/java/deltazero/amarok/ui/SecurityAuthForQuickHideActivity.java @@ -0,0 +1,38 @@ +package deltazero.amarok.ui; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; +import android.util.Log; + +import deltazero.amarok.Hider; +import deltazero.amarok.R; +import deltazero.amarok.utils.SecurityAuth; + +/** + * An empty & transparent activity with a SecurityAuth dialog only. + * If the SecurityAuth is passed, unhide files and apps. Otherwise, finish the activity. + */ +public class SecurityAuthForQuickHideActivity extends AppCompatActivity { + + private SecurityAuth securityAuth; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_empty); + + securityAuth = new SecurityAuth(this, succeed -> { + if (succeed) + new Hider(this).unhide(); + finish(); + }); + } + + @Override + protected void onStart() { + super.onStart(); + Log.i("SecurityAuth", "Start SecurityAuthForQuickHideActivity"); + securityAuth.authenticate(); + } +} \ No newline at end of file diff --git a/app/src/main/java/deltazero/amarok/ui/SwitchAppHiderActivity.java b/app/src/main/java/deltazero/amarok/ui/SwitchAppHiderActivity.java index e1b5e0c8..d8e4d304 100644 --- a/app/src/main/java/deltazero/amarok/ui/SwitchAppHiderActivity.java +++ b/app/src/main/java/deltazero/amarok/ui/SwitchAppHiderActivity.java @@ -14,6 +14,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import deltazero.amarok.AppHider.AppHiderBase; +import deltazero.amarok.AppHider.DhizukuHider; import deltazero.amarok.AppHider.DsmAppHider; import deltazero.amarok.AppHider.NoneAppHider; import deltazero.amarok.AppHider.RootAppHider; @@ -26,7 +27,7 @@ public class SwitchAppHiderActivity extends AppCompatActivity { PrefMgr prefMgr; MaterialToolbar tbToolBar; - RadioButton rbDisabled, rbRoot, rbShizuku, rbDSM; + RadioButton rbDisabled, rbRoot, rbShizuku, rbDSM, rbDhizuku; @Override protected void onCreate(Bundle savedInstanceState) { @@ -37,6 +38,7 @@ protected void onCreate(Bundle savedInstanceState) { rbRoot = findViewById(R.id.switch_apphider_radio_root); rbShizuku = findViewById(R.id.switch_apphider_radio_shizuku); rbDSM = findViewById(R.id.switch_apphider_radio_dsm); + rbDhizuku = findViewById(R.id.switch_apphider_radio_dhizuku); tbToolBar = findViewById(R.id.switch_apphider_tb_toolbar); prefMgr = new PrefMgr(this); @@ -82,6 +84,8 @@ public void onCheckAppHiderRadioButton(View view) { new ShizukuHider(this).active(this::onActivationCallback); } else if (buttonID == R.id.switch_apphider_radio_dsm) { new DsmAppHider(this).active(this::onActivationCallback); + } else if (buttonID == R.id.switch_apphider_radio_dhizuku) { + new DhizukuHider(this).active(this::onActivationCallback); } } } @@ -105,13 +109,14 @@ public void onActivationCallback(Class appHider, boolean prefMgr.setAppHiderMode(NoneAppHider.class); setCheckedRadioButton(NoneAppHider.class); - new MaterialAlertDialogBuilder(this) + runOnUiThread(() -> new MaterialAlertDialogBuilder(this) .setTitle(R.string.apphider_not_ava_title) .setMessage(msgResID) .setPositiveButton(getString(R.string.ok), null) .setNegativeButton(R.string.help, (dialog, which) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.common_error_doc_url))))) - .show(); + .show()); + } } @@ -130,27 +135,36 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { private void setCheckedRadioButton(Class appHider) { - // Thank you, ChatGPT! if (appHider.isAssignableFrom(NoneAppHider.class)) { rbDisabled.setChecked(true); rbRoot.setChecked(false); rbShizuku.setChecked(false); rbDSM.setChecked(false); + rbDhizuku.setChecked(false); } else if (appHider.isAssignableFrom(RootAppHider.class)) { rbDisabled.setChecked(false); rbRoot.setChecked(true); rbShizuku.setChecked(false); rbDSM.setChecked(false); + rbDhizuku.setChecked(false); } else if (appHider.isAssignableFrom(ShizukuHider.class)) { rbDisabled.setChecked(false); rbRoot.setChecked(false); rbShizuku.setChecked(true); rbDSM.setChecked(false); + rbDhizuku.setChecked(false); } else if (appHider.isAssignableFrom(DsmAppHider.class)) { rbDisabled.setChecked(false); rbRoot.setChecked(false); rbShizuku.setChecked(false); rbDSM.setChecked(true); + rbDhizuku.setChecked(false); + } else if (appHider.isAssignableFrom(DhizukuHider.class)) { + rbDisabled.setChecked(false); + rbRoot.setChecked(false); + rbShizuku.setChecked(false); + rbDSM.setChecked(false); + rbDhizuku.setChecked(true); } } } \ No newline at end of file diff --git a/app/src/main/java/deltazero/amarok/utils/SecurityAuth.java b/app/src/main/java/deltazero/amarok/utils/SecurityAuth.java index f5fcdd7e..4967e6da 100644 --- a/app/src/main/java/deltazero/amarok/utils/SecurityAuth.java +++ b/app/src/main/java/deltazero/amarok/utils/SecurityAuth.java @@ -17,14 +17,19 @@ public interface SecurityAuthCallback { private SecurityAuthCallback callback; private FragmentActivity activity; private PrefMgr prefMgr; + private PasswordAuthFragment passwordAuthFragment; public SecurityAuth(FragmentActivity activity, SecurityAuthCallback callback) { this.callback = callback; this.activity = activity; prefMgr = new PrefMgr(activity); + if (passwordAuthFragment == null) + passwordAuthFragment = new PasswordAuthFragment(); } public void authenticate() { + if (passwordAuthFragment.isAdded()) + passwordAuthFragment.dismiss(); if (prefMgr.getAmarokPassword() != null) { if (prefMgr.getEnableAmarokBiometricAuth()) biometricAuthenticate(); else passwordAuthenticate(); @@ -35,7 +40,7 @@ public void authenticate() { private void passwordAuthenticate() { assert prefMgr.getAmarokPassword() != null; - new PasswordAuthFragment() + passwordAuthFragment .setOnVerifiedCallback(succeed -> callback.onSecurityAuthCallback(succeed)) .show(activity.getSupportFragmentManager(), null); } diff --git a/app/src/main/res/layout/activity_empty.xml b/app/src/main/res/layout/activity_empty.xml new file mode 100644 index 00000000..41d2ef14 --- /dev/null +++ b/app/src/main/res/layout/activity_empty.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_switch_apphider.xml b/app/src/main/res/layout/activity_switch_apphider.xml index ac462117..22692e9e 100644 --- a/app/src/main/res/layout/activity_switch_apphider.xml +++ b/app/src/main/res/layout/activity_switch_apphider.xml @@ -145,6 +145,42 @@ + + + + + + + + + + + + + 密码不匹配。 生物验证 采用设备的生物验证(指纹传感器或人脸识别)来解锁 Amarok。 + Dhizuku: 权限请求被拒绝 + Dhizuku: 版本过低。请将 Dhizuku 升级到 v5.0 以上。 + Dhizuku: 服务无效。请确定您的设备已安装 Dhizuku 并成功激活。 + Dhizuku 模式 + [实验性功能] 通过 Dhizuku API 隐藏应用。\nDhizuku 通过共享设备管理员权限,赋予无 Root 设备应用隐藏的能力。需要安装并激活 Dhizuku。 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af653282..9770f438 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -132,4 +132,9 @@ Passwords do not match. Biometric authentication Use biometric authentication of the device to access Amarok when available. + Dhizuku: Permission denied + Dhizuku: Unsupported Dhizuku version. Please update Dhizuku to version 5 or higher. + Dhizuku: Service unavailable. Please ensure that you have installed and activated Dhizuku. + Dhizuku mode + [Experimental] App-hiding with Dhizuku.\nA modern and improved solution for hiding apps on non-rooted devices. Installation and activation of the Dhizuku app are required. \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 1683fb4e..f70af4e5 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -14,4 +14,13 @@ + + \ No newline at end of file