mirror of
https://github.com/ActivityLauncher/ActivityLauncher.git
synced 2026-08-17 12:53:23 +00:00
fix: #171 (Shortcuts not working propertly in Android 13)
* Fix for #174 (Shortcuts not working propertly in Android 13) * Own Launcher class and code review
This commit is contained in:
parent
ebd52fcca9
commit
9f6f78aa6d
11 changed files with 160 additions and 103 deletions
|
|
@ -43,10 +43,20 @@
|
|||
<activity
|
||||
android:name=".RootLauncherActivity"
|
||||
android:label="@string/context_action_launch_as_root"
|
||||
android:exported="true" >
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ShortcutActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="activitylauncher.intent.action.LAUNCH_SHORTCUT"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
83
ActivityLauncherApp/src/main/java/org/thirdparty/Launcher.java
vendored
Normal file
83
ActivityLauncherApp/src/main/java/org/thirdparty/Launcher.java
vendored
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
|
||||
<item name="dialogCornerRadius">24dp</item>
|
||||
</style>
|
||||
</resources>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
|
||||
<item name="dialogCornerRadius">24dp</item>
|
||||
</style>
|
||||
<style name="AppTheme.NoDisplay" parent="AppTheme">
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
<item name="android:windowDisablePreview">true</item>
|
||||
<item name="android:windowNoDisplay">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
1
gradle/wrapper/gradle-wrapper.properties
vendored
1
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue