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 extends AppHiderBase> 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 extends AppHiderBase> 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 extends AppHiderBase> 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