diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4355842d..54ed9803 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,7 +23,6 @@
android:theme="@style/Theme.ActivityLauncher">
-
@@ -31,21 +30,14 @@
android:name=".SettingsActivity"
android:label="@string/activity_settings"
android:exported="false" />
-
-
-
-
-
+
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/ShortcutActivity.kt b/app/src/main/java/de/szalkowski/activitylauncher/ShortcutActivity.kt
index 45661bda..d834ca8d 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/ShortcutActivity.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/ShortcutActivity.kt
@@ -3,27 +3,41 @@ package de.szalkowski.activitylauncher
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
import dagger.hilt.android.AndroidEntryPoint
import de.szalkowski.activitylauncher.services.ActivityLauncherService
+import de.szalkowski.activitylauncher.services.IconCreatorService
+import de.szalkowski.activitylauncher.services.IntentSigningService
import javax.inject.Inject
@AndroidEntryPoint
class ShortcutActivity : AppCompatActivity() {
@Inject
- internal lateinit var activityLauncherService: ActivityLauncherService
+ internal lateinit var launcherService: ActivityLauncherService
+
+ @Inject
+ internal lateinit var signingService: IntentSigningService
+
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
- val launchIntent = Intent.parseUri(intent.getStringExtra("extra_intent"), 0)
- activityLauncherService.launchActivity(launchIntent.component!!,
- asRoot = false,
+ val launchIntent = Intent.parseUri(intent.getStringExtra(IconCreatorService.INTENT_EXTRA_INTENT), 0)
+ val signature = intent.getStringExtra(IconCreatorService.INTENT_EXTRA_SIGNATURE).orEmpty()
+ val asRoot = intent.action == IconCreatorService.INTENT_LAUNCH_ROOT_SHORTCUT
+
+ if (asRoot && !signingService.validateIntentSignature(launchIntent, signature)) {
+ return
+ }
+
+ launcherService.launchActivity(launchIntent.component!!,
+ asRoot,
showToast = false
- );
+ )
} catch (e: Exception) {
e.printStackTrace()
} finally {
- finish()
+ ActivityCompat.finishAffinity(this);
}
}
}
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/services/ActivityLauncherService.kt b/app/src/main/java/de/szalkowski/activitylauncher/services/ActivityLauncherService.kt
index 8a7d17cb..a140be0b 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/services/ActivityLauncherService.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/services/ActivityLauncherService.kt
@@ -34,7 +34,7 @@ class ActivityLauncherServiceImpl @Inject constructor(@ActivityContext private v
asRoot: Boolean,
showToast: Boolean
) {
- val intent: Intent = getActivityIntent(activity, null)
+ val intent = getActivityIntent(activity, null)
if (showToast) Toast.makeText(
context,
String.format(
@@ -69,6 +69,7 @@ class ActivityLauncherServiceImpl @Inject constructor(@ActivityContext private v
component
)
}
+
val process = Runtime.getRuntime().exec(
arrayOf(
"su", "-c",
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/services/Bindings.kt b/app/src/main/java/de/szalkowski/activitylauncher/services/Bindings.kt
index 61f6db4c..8938d328 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/services/Bindings.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/services/Bindings.kt
@@ -34,6 +34,12 @@ abstract class ServicesModule {
@Module
@InstallIn(SingletonComponent::class)
abstract class ApplicationServicesModule {
+ @Singleton
+ @Binds
+ abstract fun bindIntentSigningService(
+ intentSigningServiceImpl: IntentSigningServiceImpl
+ ): IntentSigningService
+
@Singleton
@Binds
abstract fun bindRootDetectionService(
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/services/IconCreatorService.kt b/app/src/main/java/de/szalkowski/activitylauncher/services/IconCreatorService.kt
index 6fb78d34..a918e20a 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/services/IconCreatorService.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/services/IconCreatorService.kt
@@ -21,42 +21,45 @@ import androidx.appcompat.app.AlertDialog
import dagger.hilt.android.qualifiers.ActivityContext
import de.szalkowski.activitylauncher.R
import de.szalkowski.activitylauncher.services.internal.getActivityIntent
-import java.util.Objects
import javax.inject.Inject
-private const val INTENT_LAUNCH_SHORTCUT = "activitylauncher.intent.action.LAUNCH_SHORTCUT"
-
interface IconCreatorService {
- fun createLauncherIcon(activity: MyActivityInfo, extras: Bundle?)
- fun createLauncherIcon(activity: MyActivityInfo)
- fun createLauncherIcon(pack: MyPackageInfo)
+ fun createLauncherIcon(activity: MyActivityInfo, optionalExtras: Bundle? = null)
+ fun createRootLauncherIcon(activity: MyActivityInfo, optionalExtras: Bundle? = null)
+
+ companion object {
+ const val INTENT_LAUNCH_SHORTCUT = "activitylauncher.intent.action.LAUNCH_SHORTCUT"
+ const val INTENT_LAUNCH_ROOT_SHORTCUT = "activitylauncher.intent.action.LAUNCH_ROOT_SHORTCUT"
+
+ const val INTENT_EXTRA_INTENT = "extra_intent"
+ const val INTENT_EXTRA_SIGNATURE = "sign"
+ }
}
-class IconCreatorServiceImpl @Inject constructor(@ActivityContext private val context: Context) :
- IconCreatorService {
- override fun createLauncherIcon(activity: MyActivityInfo, extras: Bundle?) {
+class IconCreatorServiceImpl @Inject constructor(
+ @ActivityContext private val context: Context,
+ private val signingService: IntentSigningService
+) : IconCreatorService {
+ override fun createLauncherIcon(activity: MyActivityInfo, optionalExtras: Bundle?) {
+ createLauncherIcon(activity, optionalExtras, false)
+ }
+
+ override fun createRootLauncherIcon(activity: MyActivityInfo, optionalExtras: Bundle?) {
+ createLauncherIcon(activity, optionalExtras, true)
+ }
+
+ private fun createLauncherIcon(activity: MyActivityInfo, optionalExtras: Bundle?, asRoot: Boolean) {
val pack = extractIconPackageName(activity)
- val name: String = activity.name
- val intent = getActivityIntent(activity.componentName, extras)
- val icon: Drawable = activity.icon
+ val intent = getActivityIntent(activity.componentName, optionalExtras)
// Use bitmap version, if icon from different package is used
if (pack != null && pack != activity.componentName.packageName) {
- createShortcut(name, icon, intent, null)
+ createShortcut(activity.name, intent, activity.icon, asRoot, null)
} else {
- createShortcut(name, icon, intent, activity.iconResourceName)
+ createShortcut(activity.name, intent, activity.icon, asRoot, activity.iconResourceName)
}
}
- override fun createLauncherIcon(activity: MyActivityInfo) {
- createLauncherIcon(activity, null)
- }
-
- override fun createLauncherIcon(pack: MyPackageInfo) {
- val intent = context.packageManager.getLaunchIntentForPackage(pack.packageName) ?: return
- createShortcut(pack.name, pack.icon, intent, pack.iconResourceName)
- }
-
private fun extractIconPackageName(
activity: MyActivityInfo,
): String? {
@@ -105,7 +108,7 @@ class IconCreatorServiceImpl @Inject constructor(@ActivityContext private val co
}
private fun createShortcut(
- appName: String, draw: Drawable, intent: Intent, iconResourceName: String?
+ appName: String, intent: Intent, draw: Drawable, asRoot: Boolean, iconResourceName: String?
) {
Toast.makeText(
context, String.format(
@@ -113,17 +116,22 @@ class IconCreatorServiceImpl @Inject constructor(@ActivityContext private val co
), Toast.LENGTH_LONG
).show()
if (Build.VERSION.SDK_INT >= 26) {
- doCreateShortcut(appName, draw, intent)
+ doCreateShortcut(appName, intent, asRoot, draw)
} else {
- doCreateShortcut(appName, intent, iconResourceName)
+ doCreateShortcut(appName, intent, asRoot, iconResourceName)
}
}
private fun doCreateShortcut(
- appName: String, intent: Intent, iconResourceName: String?
+ appName: String, intent: Intent, asRoot: Boolean, iconResourceName: String?
) {
val shortcutIntent = Intent()
- shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent)
+ if (asRoot) {
+ // wrap only if root access needed
+ shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, createShortcutIntent(intent, true))
+ } else {
+ shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent)
+ }
shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, appName)
if (iconResourceName != null) {
val ir = ShortcutIconResource()
@@ -134,6 +142,7 @@ class IconCreatorServiceImpl @Inject constructor(@ActivityContext private val co
}
ir.resourceName = iconResourceName
shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, ir)
+
}
shortcutIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT")
context.sendBroadcast(shortcutIntent)
@@ -141,20 +150,15 @@ class IconCreatorServiceImpl @Inject constructor(@ActivityContext private val co
@TargetApi(26)
private fun doCreateShortcut(
- appName: String, draw: Drawable, extraIntent: Intent
+ appName: String, intent: Intent, asRoot: Boolean, draw: Drawable
) {
- val shortcutManager = Objects.requireNonNull(
- context.getSystemService(
- ShortcutManager::class.java
- )
- )
+ val shortcutManager = context.getSystemService(ShortcutManager::class.java)!!
if (shortcutManager.isRequestPinShortcutSupported) {
val icon = getIconFromDrawable(draw)
- val intent = Intent(INTENT_LAUNCH_SHORTCUT)
- intent.putExtra("extra_intent", extraIntent.toUri(0))
+ val shortcutIntent = createShortcutIntent(intent, asRoot)
val shortcutInfo =
ShortcutInfo.Builder(context, appName).setShortLabel(appName).setLongLabel(appName)
- .setIcon(icon).setIntent(intent).build()
+ .setIcon(icon).setIntent(shortcutIntent).build()
shortcutManager.requestPinShortcut(shortcutInfo, null)
} else {
AlertDialog.Builder(context).setTitle(context.getText(R.string.error_creating_shortcut))
@@ -166,5 +170,25 @@ class IconCreatorServiceImpl @Inject constructor(@ActivityContext private val co
}.show()
}
}
+
+ private fun createShortcutIntent(intent: Intent, asRoot: Boolean): Intent {
+ val action = if(asRoot) {
+ IconCreatorService.INTENT_LAUNCH_ROOT_SHORTCUT} else {IconCreatorService.INTENT_LAUNCH_SHORTCUT}
+ val shortcutIntent = Intent(action)
+ shortcutIntent.putExtra(IconCreatorService.INTENT_EXTRA_INTENT, intent.toUri(0))
+
+ val signature: String
+ try {
+ signature = signingService.signIntent(intent)
+ shortcutIntent.putExtra(IconCreatorService.INTENT_EXTRA_SIGNATURE, signature)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Toast.makeText(
+ context, context.getText(R.string.error).toString() + ": " + e, Toast.LENGTH_LONG
+ ).show()
+ }
+
+ return shortcutIntent
+ }
}
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/services/IntentSigningService.kt b/app/src/main/java/de/szalkowski/activitylauncher/services/IntentSigningService.kt
index 9239e853..4070a4a3 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/services/IntentSigningService.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/services/IntentSigningService.kt
@@ -1,52 +1,59 @@
-package de.szalkowski.activitylauncher.todo;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Base64;
-
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
+package de.szalkowski.activitylauncher.services;
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.util.Base64
+import dagger.hilt.android.qualifiers.ApplicationContext
+import java.security.SecureRandom
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+import javax.inject.Inject
+
+
+interface IntentSigningService {
+ fun signIntent(intent: Intent): String
+ fun validateIntentSignature(intent: Intent, signature: String): Boolean
+}
-public class Signer {
- private final String key;
+class IntentSigningServiceImpl @Inject constructor(@ApplicationContext context: Context) :
+ IntentSigningService {
+ private val key: String
- public Signer(Context context) {
- SharedPreferences preferences = context.getSharedPreferences("signer", Context.MODE_PRIVATE);
+ init {
+ val preferences = context.getSharedPreferences("signer", Context.MODE_PRIVATE)
if (!preferences.contains("key")) {
- SecureRandom random = new SecureRandom();
- byte[] bytes = new byte[256];
- random.nextBytes(bytes);
-
- this.key = Base64.encodeToString(bytes, Base64.NO_WRAP);
- preferences.edit().putString("key", this.key).apply();
+ val random = SecureRandom()
+ val bytes = ByteArray(256)
+ random.nextBytes(bytes)
+ key = Base64.encodeToString(bytes, Base64.NO_WRAP)
+ preferences.edit().putString("key", key).apply()
} else {
- this.key = preferences.getString("key", "");
+ key = preferences.getString("key", "")!!
}
}
- /**
- * Adapted from StackOverflow:
- * https://stackoverflow.com/questions/36004761/is-there-any-function-for-creating-hmac256-string-in-android
- */
- private static String hmac256(String key, String message) throws NoSuchAlgorithmException, InvalidKeyException {
- Mac mac = Mac.getInstance("HmacSHA256");
- mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
- byte[] result = mac.doFinal(message.getBytes());
- return Base64.encodeToString(result, Base64.NO_WRAP);
+ override fun signIntent(intent: Intent): String {
+ val uri = intent.toUri(0)
+ return hmac256(key, uri)
}
- public String signComponentName(ComponentName comp) throws InvalidKeyException, NoSuchAlgorithmException {
- String name = comp.flattenToShortString();
- return hmac256(this.key, name);
+ override fun validateIntentSignature(intent: Intent, signature: String): Boolean {
+ val compSignature = signIntent(intent)
+ return signature == compSignature
}
- public boolean validateComponentNameSignature(ComponentName comp, String signature) throws InvalidKeyException, NoSuchAlgorithmException {
- String compSignature = this.signComponentName(comp);
- return signature.equals(compSignature);
+ companion object {
+ /**
+ * Adapted from StackOverflow:
+ * https://stackoverflow.com/questions/36004761/is-there-any-function-for-creating-hmac256-string-in-android
+ */
+ private fun hmac256(key: String?, message: String): String {
+ val mac = Mac.getInstance("HmacSHA256")
+ mac.init(SecretKeySpec(key!!.toByteArray(), "HmacSHA256"))
+ val result = mac.doFinal(message.toByteArray())
+ return Base64.encodeToString(result, Base64.NO_WRAP)
+ }
}
}
+
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/services/internal/ActivityIntent.kt b/app/src/main/java/de/szalkowski/activitylauncher/services/internal/ActivityIntent.kt
index b491e1e0..64522ed8 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/services/internal/ActivityIntent.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/services/internal/ActivityIntent.kt
@@ -7,8 +7,7 @@ import android.os.Bundle
fun getActivityIntent(activity: ComponentName?, extras: Bundle?): Intent {
val intent = Intent()
intent.setComponent(activity)
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
if (extras != null) {
intent.putExtras(extras)
}
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/ui/ActivityDetailsFragment.kt b/app/src/main/java/de/szalkowski/activitylauncher/ui/ActivityDetailsFragment.kt
index 2ca882f1..f4446238 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/ui/ActivityDetailsFragment.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/ui/ActivityDetailsFragment.kt
@@ -63,11 +63,21 @@ class ActivityDetailsFragment : Fragment() {
iconCreatorService.createLauncherIcon(editedActivityInfo)
}
+ binding.btCreateShortcutAsRoot.setOnClickListener {
+ iconCreatorService.createRootLauncherIcon(editedActivityInfo)
+ }
+
binding.btLaunch.setOnClickListener {
activityLauncherService.launchActivity(
editedActivityInfo.componentName, asRoot = false, showToast = true
)
}
+
+ binding.btLaunchAsRoot.setOnClickListener {
+ activityLauncherService.launchActivity(
+ editedActivityInfo.componentName, asRoot = true, showToast = true
+ )
+ }
}
override fun onDestroyView() {
diff --git a/app/src/main/java/de/szalkowski/activitylauncher/ui/PackageListFragment.kt b/app/src/main/java/de/szalkowski/activitylauncher/ui/PackageListFragment.kt
index 28105e2e..0633e92b 100644
--- a/app/src/main/java/de/szalkowski/activitylauncher/ui/PackageListFragment.kt
+++ b/app/src/main/java/de/szalkowski/activitylauncher/ui/PackageListFragment.kt
@@ -38,6 +38,7 @@ class PackageListFragment : Fragment() {
}
binding.rvPackages.adapter = packageListAdapter
binding.rvPackages.layoutManager = LinearLayoutManager(requireContext())
+ binding.rvPackages.isNestedScrollingEnabled = false
}
override fun onDestroyView() {