diff --git a/ActivityLauncherApp/src/main/AndroidManifest.xml b/ActivityLauncherApp/src/main/AndroidManifest.xml index cd6f96a..7431245 100644 --- a/ActivityLauncherApp/src/main/AndroidManifest.xml +++ b/ActivityLauncherApp/src/main/AndroidManifest.xml @@ -43,10 +43,20 @@ + android:exported="true" + android:theme="@style/AppTheme.NoDisplay"> + + + + + + diff --git a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/AllTasksListFragment.java b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/AllTasksListFragment.java index d47bab8..dc3fe6a 100644 --- a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/AllTasksListFragment.java +++ b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/AllTasksListFragment.java @@ -21,7 +21,8 @@ import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; -import org.thirdparty.LauncherIconCreator; +import org.thirdparty.IconCreator; +import org.thirdparty.Launcher; import de.szalkowski.activitylauncher.databinding.FragmentAllListBinding; @@ -38,7 +39,7 @@ public class AllTasksListFragment extends Fragment implements AllTasksListAsyncP ExpandableListAdapter adapter = parent.getExpandableListAdapter(); MyActivityInfo info = (MyActivityInfo) adapter.getChild(groupPosition, childPosition); var rooted = isRootAllowed(); - LauncherIconCreator.launchActivity(getActivity(), info.component_name, rooted && info.is_private); + Launcher.launchActivity(getActivity(), info.component_name, rooted && info.is_private); return false; } ); @@ -92,16 +93,16 @@ public class AllTasksListFragment extends Fragment implements AllTasksListAsyncP MyActivityInfo activity = (MyActivityInfo) binding.expandableListView1.getExpandableListAdapter().getChild(ExpandableListView.getPackedPositionGroup(info.packedPosition), ExpandableListView.getPackedPositionChild(info.packedPosition)); switch (item.getItemId()) { case 0: - LauncherIconCreator.createLauncherIcon(getActivity(), activity); + IconCreator.createLauncherIcon(getActivity(), activity); break; case 1: RootLauncherIconCreator.createLauncherIcon(getActivity(), activity); break; case 2: - LauncherIconCreator.launchActivity(getActivity(), activity.component_name, false); + Launcher.launchActivity(getActivity(), activity.component_name, false); break; case 3: - LauncherIconCreator.launchActivity(getActivity(), activity.component_name, true); + Launcher.launchActivity(getActivity(), activity.component_name, true); break; case 4: DialogFragment dialog = new ShortcutEditDialogFragment(); @@ -118,7 +119,7 @@ public class AllTasksListFragment extends Fragment implements AllTasksListAsyncP MyPackageInfo pack = (MyPackageInfo) binding.expandableListView1.getExpandableListAdapter().getGroup(ExpandableListView.getPackedPositionGroup(info.packedPosition)); switch (item.getItemId()) { case 0: - LauncherIconCreator.createLauncherIcon(requireActivity(), pack); + IconCreator.createLauncherIcon(requireActivity(), pack); Toast.makeText(getActivity(), getString(R.string.error_no_default_activity), Toast.LENGTH_LONG).show(); break; case 2: diff --git a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherActivity.java b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherActivity.java index d4a537d..2f69c14 100644 --- a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherActivity.java +++ b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherActivity.java @@ -4,11 +4,11 @@ import android.content.ComponentName; import android.os.Bundle; import android.widget.Toast; -import androidx.fragment.app.FragmentActivity; +import androidx.appcompat.app.AppCompatActivity; -import org.thirdparty.LauncherIconCreator; +import org.thirdparty.Launcher; -public class RootLauncherActivity extends FragmentActivity { +public class RootLauncherActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -28,7 +28,7 @@ public class RootLauncherActivity extends FragmentActivity { var signer = new Signer(getApplicationContext()); if (signer.validateComponentNameSignature(componentName, signature)) { - LauncherIconCreator.launchActivity(getApplicationContext(), componentName, true); + Launcher.launchActivity(getApplicationContext(), componentName, true); } } catch (Exception e) { e.printStackTrace(); diff --git a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherIconCreator.java b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherIconCreator.java index c130eb5..e71f57a 100644 --- a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherIconCreator.java +++ b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/RootLauncherIconCreator.java @@ -5,7 +5,7 @@ import android.content.Context; import android.os.Bundle; import android.widget.Toast; -import org.thirdparty.LauncherIconCreator; +import org.thirdparty.IconCreator; public class RootLauncherIconCreator { public static void createLauncherIcon(Context context, MyActivityInfo activity) { @@ -29,6 +29,6 @@ public class RootLauncherIconCreator { activity.is_private = true; activity.component_name = new ComponentName("de.szalkowski.activitylauncher", "de.szalkowski.activitylauncher.RootLauncherActivity"); - LauncherIconCreator.createLauncherIcon(context, activity, extras); + IconCreator.createLauncherIcon(context, activity, extras); } } diff --git a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/ShortcutActivity.java b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/ShortcutActivity.java new file mode 100644 index 0000000..34df434 --- /dev/null +++ b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/ShortcutActivity.java @@ -0,0 +1,27 @@ +package de.szalkowski.activitylauncher; + +import static org.thirdparty.Launcher.launchActivity; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import java.net.URISyntaxException; + +public class ShortcutActivity extends AppCompatActivity { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + Intent launchIntent = Intent.parseUri(getIntent().getStringExtra("extra_intent"), 0); + launchActivity(this, launchIntent.getComponent(), false); + } catch (URISyntaxException e) { + e.printStackTrace(); + } finally { + finish(); + } + } +} diff --git a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/ShortcutEditDialogFragment.java b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/ShortcutEditDialogFragment.java index 77d10fa..7060014 100644 --- a/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/ShortcutEditDialogFragment.java +++ b/ActivityLauncherApp/src/main/java/de/szalkowski/activitylauncher/ShortcutEditDialogFragment.java @@ -12,7 +12,6 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; -import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; @@ -21,7 +20,7 @@ import androidx.core.content.res.ResourcesCompat; import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; -import org.thirdparty.LauncherIconCreator; +import org.thirdparty.IconCreator; import java.util.Objects; @@ -134,7 +133,7 @@ public class ShortcutEditDialogFragment extends DialogFragment { if (as_root) { RootLauncherIconCreator.createLauncherIcon(getActivity(), ShortcutEditDialogFragment.this.activity); } else { - LauncherIconCreator.createLauncherIcon(getActivity(), ShortcutEditDialogFragment.this.activity); + IconCreator.createLauncherIcon(getActivity(), ShortcutEditDialogFragment.this.activity); } }) .setNegativeButton(android.R.string.cancel, (dialog, which) -> Objects.requireNonNull(ShortcutEditDialogFragment.this.getDialog()).cancel()); diff --git a/ActivityLauncherApp/src/main/java/org/thirdparty/LauncherIconCreator.java b/ActivityLauncherApp/src/main/java/org/thirdparty/IconCreator.java similarity index 68% rename from ActivityLauncherApp/src/main/java/org/thirdparty/LauncherIconCreator.java rename to ActivityLauncherApp/src/main/java/org/thirdparty/IconCreator.java index 3bd4829..e30246b 100644 --- a/ActivityLauncherApp/src/main/java/org/thirdparty/LauncherIconCreator.java +++ b/ActivityLauncherApp/src/main/java/org/thirdparty/IconCreator.java @@ -28,24 +28,20 @@ import android.os.Build; import android.os.Bundle; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.StandardCharsets; import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; + import de.szalkowski.activitylauncher.MyActivityInfo; import de.szalkowski.activitylauncher.MyPackageInfo; import de.szalkowski.activitylauncher.R; -public class LauncherIconCreator { +public class IconCreator { - private static Intent getActivityIntent(ComponentName activity, Bundle extras) { + private static String INTENT_LAUNCH_SHORTCUT = "activitylauncher.intent.action.LAUNCH_SHORTCUT"; + + public static Intent getActivityIntent(ComponentName activity, Bundle extras) { Intent intent = new Intent(); intent.setComponent(activity); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -130,72 +126,6 @@ public class LauncherIconCreator { return Icon.createWithBitmap(bmp); } - /** - * Got reference from stackoverflow.com URL - * https://stackoverflow.com/questions/9194725/run-android-program-as-root - * https://stackoverflow.com/questions/12343227/escaping-bash-function-arguments-for-use-by-su-c - */ - public static void launchActivity(Context context, ComponentName activity, boolean asRoot) { - Intent intent = LauncherIconCreator.getActivityIntent(activity, null); - Toast.makeText(context, String.format(context.getText(R.string.starting_activity).toString(), activity.flattenToShortString()), - Toast.LENGTH_LONG).show(); - - try { - if (!asRoot) { - context.startActivity(intent); - } else { - startRootActivity(context, activity); - } - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(context, context.getText(R.string.error).toString() + ": " + e, Toast.LENGTH_LONG).show(); - } - } - - private static void startRootActivity(Context context, ComponentName activity) throws IOException, InterruptedException, IllegalArgumentException { - var component = activity.flattenToShortString(); - boolean isValid = validateComponentName(component); - if (!isValid) { - throw new IllegalArgumentException(String.format(context.getString(R.string.exception_invalid_component_name), component)); - } - Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", "am start -n " + component}); - String output = getProcessOutput(process); - - var exitValue = process.waitFor(); - if (exitValue > 0) { - throw new RuntimeException(String.format(context.getString(R.string.exception_command_error), exitValue, output)); - } - } - - /** - * Got reference from stackoverflow.com URL: - * https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java - */ - @NonNull - private static String getProcessOutput(Process process) throws IOException { - var stream = process.getErrorStream(); - int bufferSize = 1024; - char[] buffer = new char[bufferSize]; - StringBuilder out = new StringBuilder(); - Reader in = new InputStreamReader(stream, StandardCharsets.UTF_8); - for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) { - out.append(buffer, 0, numRead); - } - return out.toString(); - } - - /** - * In order to be on the safe side, validate component name before merging it into a root shell command - * - * @param component component name - * @return true, if valid - */ - private static boolean validateComponentName(String component) { - Pattern p = Pattern.compile("^[./a-zA-Z0-9]+$"); - Matcher m = p.matcher(component); - return m.matches(); - } - private static void createShortcut(Context context, String appName, Drawable draw, Intent intent, String iconResourceName) { Toast.makeText(context, String.format(context.getText(R.string.creating_application_shortcut).toString(), appName), @@ -208,7 +138,6 @@ public class LauncherIconCreator { } } - @TargetApi(19) private static void doCreateShortcut(Context context, String appName, Intent intent, String iconResourceName) { Intent shortcutIntent = new Intent(); shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent); @@ -228,12 +157,13 @@ public class LauncherIconCreator { } @TargetApi(26) - private static void doCreateShortcut(Context context, String appName, Drawable draw, Intent intent) { + private static void doCreateShortcut(Context context, String appName, Drawable draw, Intent extraIntent) { ShortcutManager shortcutManager = Objects.requireNonNull(context.getSystemService(ShortcutManager.class)); if (shortcutManager.isRequestPinShortcutSupported()) { Icon icon = getIconFromDrawable(draw); - intent.setAction(Intent.ACTION_CREATE_SHORTCUT); + Intent intent = new Intent(INTENT_LAUNCH_SHORTCUT); + intent.putExtra("extra_intent", extraIntent.toUri(0)); ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(context, appName) .setShortLabel(appName) @@ -241,8 +171,8 @@ public class LauncherIconCreator { .setIcon(icon) .setIntent(intent) .build(); - shortcutManager.requestPinShortcut(shortcutInfo, null); + } else { new AlertDialog.Builder(context) .setTitle(context.getText(R.string.error_creating_shortcut)) diff --git a/ActivityLauncherApp/src/main/java/org/thirdparty/Launcher.java b/ActivityLauncherApp/src/main/java/org/thirdparty/Launcher.java new file mode 100644 index 0000000..8794d5c --- /dev/null +++ b/ActivityLauncherApp/src/main/java/org/thirdparty/Launcher.java @@ -0,0 +1,83 @@ +package org.thirdparty; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.szalkowski.activitylauncher.R; + +public class Launcher { + /** + * Got reference from stackoverflow.com URL + * https://stackoverflow.com/questions/9194725/run-android-program-as-root + * https://stackoverflow.com/questions/12343227/escaping-bash-function-arguments-for-use-by-su-c + */ + public static void launchActivity(Context context, ComponentName activity, boolean asRoot) { + Intent intent = IconCreator.getActivityIntent(activity, null); + Toast.makeText(context, String.format(context.getText(R.string.starting_activity).toString(), activity.flattenToShortString()), + Toast.LENGTH_LONG).show(); + try { + if (!asRoot) { + context.startActivity(intent); + } else { + startRootActivity(context, activity); + } + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(context, context.getText(R.string.error).toString() + ": " + e, Toast.LENGTH_LONG).show(); + } + } + + private static void startRootActivity(Context context, ComponentName activity) throws IOException, InterruptedException, IllegalArgumentException { + var component = activity.flattenToShortString(); + boolean isValid = validateComponentName(component); + if (!isValid) { + throw new IllegalArgumentException(String.format(context.getString(R.string.exception_invalid_component_name), component)); + } + Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", "am start -n " + component}); + String output = getProcessOutput(process); + + var exitValue = process.waitFor(); + if (exitValue > 0) { + throw new RuntimeException(String.format(context.getString(R.string.exception_command_error), exitValue, output)); + } + } + /** + * Got reference from stackoverflow.com URL: + * https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java + */ + @NonNull + private static String getProcessOutput(Process process) throws IOException { + var stream = process.getErrorStream(); + int bufferSize = 1024; + char[] buffer = new char[bufferSize]; + StringBuilder out = new StringBuilder(); + Reader in = new InputStreamReader(stream, StandardCharsets.UTF_8); + for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) { + out.append(buffer, 0, numRead); + } + return out.toString(); + } + + /** + * In order to be on the safe side, validate component name before merging it into a root shell command + * + * @param component component name + * @return true, if valid + */ + private static boolean validateComponentName(String component) { + Pattern p = Pattern.compile("^[./a-zA-Z0-9]+$"); + Matcher m = p.matcher(component); + return m.matches(); + } +} diff --git a/ActivityLauncherApp/src/main/res/values/themes.xml b/ActivityLauncherApp/src/main/res/values/themes.xml index becde7d..7b5e095 100644 --- a/ActivityLauncherApp/src/main/res/values/themes.xml +++ b/ActivityLauncherApp/src/main/res/values/themes.xml @@ -1,6 +1,14 @@ - - - - + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index d21b62e..7f7e1ce 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' + classpath 'com.android.tools.build:gradle:7.3.1' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b916c04..ae04661 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists