Skip to content

Commit 230f135

Browse files
committed
[AppDetails] Allow managing some permissions in no-root mode
- In the "Uses permissions" tab, for some permissions, there is a "setting" icon which can used to easily grant or revoke a permission. - In the "App Info" tab, battery optimization and net policy settings are enabled in no-root mode, and the user is redirected to the corresponding settings page when clicked. However, on some devices, a few of these settings pages may be unavailable, and an error will be shown instead. Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
1 parent f186c4f commit 230f135

6 files changed

Lines changed: 182 additions & 22 deletions

File tree

app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsPermissionsFragment.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import androidx.annotation.Nullable;
2727
import androidx.annotation.UiThread;
2828

29+
import com.google.android.material.button.MaterialButton;
2930
import com.google.android.material.card.MaterialCardView;
3031
import com.google.android.material.chip.Chip;
3132
import com.google.android.material.materialswitch.MaterialSwitch;
@@ -418,6 +419,7 @@ class ViewHolder extends RecyclerView.ViewHolder {
418419
TextView textView8;
419420
ImageView imageView;
420421
MaterialSwitch toggleSwitch;
422+
MaterialButton settingButton;
421423
Chip chipType;
422424

423425
public ViewHolder(@NonNull View itemView) {
@@ -456,6 +458,7 @@ public ViewHolder(@NonNull View itemView) {
456458
textView4 = itemView.findViewById(R.id.perm_package_name);
457459
textView5 = itemView.findViewById(R.id.perm_group);
458460
toggleSwitch = itemView.findViewById(R.id.perm_toggle_btn);
461+
settingButton = itemView.findViewById(R.id.action_settings);
459462
break;
460463
default:
461464
break;
@@ -713,6 +716,22 @@ private void getUsesPermissionsView(@NonNull Context context, @NonNull ViewHolde
713716
holder.toggleSwitch.setVisibility(View.GONE);
714717
holder.itemView.setOnClickListener(null);
715718
holder.itemView.setClickable(false);
719+
if (permissionItem.settingItem != null) {
720+
holder.settingButton.setVisibility(View.VISIBLE);
721+
holder.settingButton.setOnClickListener(v -> {
722+
try {
723+
String packageName = Objects.requireNonNull(viewModel).getPackageName();
724+
startActivity(permissionItem.settingItem.toIntent(Objects.requireNonNull(packageName)));
725+
} catch (Throwable th) {
726+
th.printStackTrace();
727+
if (th.getLocalizedMessage() != null) {
728+
UIUtils.displayLongToast(th.getLocalizedMessage());
729+
}
730+
}
731+
});
732+
} else {
733+
holder.settingButton.setVisibility(View.GONE);
734+
}
716735
}
717736
int flags = permissionItem.permission.getFlags();
718737
holder.itemView.setOnLongClickListener(flags == 0 ? null : v -> {

app/src/main/java/io/github/muntashirakon/AppManager/details/info/AppInfoFragment.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,14 +406,14 @@ public void onPrepareMenu(@NonNull Menu menu) {
406406
runInTermuxMenu.setVisible(isDebuggable);
407407
}
408408
if (batteryOptMenu != null) {
409-
batteryOptMenu.setVisible(SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.DEVICE_POWER));
409+
batteryOptMenu.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
410410
}
411411
if (sensorsMenu != null) {
412412
sensorsMenu.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
413413
&& SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.MANAGE_SENSORS));
414414
}
415415
if (netPolicyMenu != null) {
416-
netPolicyMenu.setVisible(SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.MANAGE_NETWORK_POLICY));
416+
netPolicyMenu.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
417417
}
418418
if (installMenu != null) {
419419
installMenu.setVisible(Users.getUsersIds().length > 1 && SelfPermissions.canInstallExistingPackages());
@@ -525,8 +525,12 @@ public boolean onMenuItemSelected(@NonNull MenuItem item) {
525525
}
526526
})
527527
.show();
528-
} else {
529-
Log.e(TAG, "No DUMP permission.");
528+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
529+
try {
530+
startActivity(IntentUtils.getBatteryOptSettings(mPackageName));
531+
} catch (Throwable th) {
532+
UIUtils.displayShortToast("No DEVICE_POWER permission.");
533+
}
530534
}
531535
} else if (itemId == R.id.action_sensor) {
532536
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.MANAGE_SENSORS)) {
@@ -568,6 +572,16 @@ public boolean onMenuItemSelected(@NonNull MenuItem item) {
568572
UIUtils.displayLongToast(R.string.netpolicy_cannot_be_modified_for_core_apps);
569573
return true;
570574
}
575+
if (!SelfPermissions.checkSelfOrRemotePermission(ManifestCompat.permission.MANAGE_NETWORK_POLICY)) {
576+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
577+
try {
578+
startActivity(IntentUtils.getNetPolicySettings(mPackageName));
579+
} catch (Throwable th) {
580+
UIUtils.displayShortToast("No MANAGE_NETWORK_POLICY permission.");
581+
}
582+
}
583+
return true;
584+
}
571585
ArrayMap<Integer, String> netPolicyMap = NetworkPolicyManagerCompat.getAllReadablePolicies(ContextUtils.getContext());
572586
Integer[] polices = new Integer[netPolicyMap.size()];
573587
CharSequence[] policyStrings = new String[netPolicyMap.size()];
@@ -1019,6 +1033,9 @@ private List<TagItem> getTagCloudItems(@NonNull AppInfoViewModel.TagCloud tagClo
10191033
}
10201034
})
10211035
.show());
1036+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1037+
batteryOptTag.setOnClickListener(v -> ExUtils.exceptionAsIgnored(() ->
1038+
startActivity(IntentUtils.getBatteryOptSettings(mPackageName))));
10221039
}
10231040
}
10241041
if (!tagCloud.sensorsEnabled) {

app/src/main/java/io/github/muntashirakon/AppManager/details/struct/AppDetailsPermissionItem.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.os.RemoteException;
88

99
import androidx.annotation.NonNull;
10+
import androidx.annotation.Nullable;
1011
import androidx.annotation.WorkerThread;
1112
import androidx.core.content.pm.PermissionInfoCompat;
1213

@@ -25,6 +26,8 @@ public class AppDetailsPermissionItem extends AppDetailsItem<PermissionInfo> {
2526
public final boolean modifiable;
2627
public final int flags;
2728
public final int protectionFlags;
29+
@Nullable
30+
public final PermUtils.SettingItem settingItem;
2831

2932
public AppDetailsPermissionItem(@NonNull PermissionInfo permissionInfo, @NonNull Permission permission, int flags) {
3033
super(permissionInfo);
@@ -33,6 +36,7 @@ public AppDetailsPermissionItem(@NonNull PermissionInfo permissionInfo, @NonNull
3336
this.protectionFlags = PermissionInfoCompat.getProtectionFlags(permissionInfo);
3437
this.modifiable = PermUtils.isModifiable(permission);
3538
this.flags = flags;
39+
this.settingItem = PermUtils.permissionNameToSettingItem.get(permissionInfo.name);
3640
}
3741

3842
public boolean isGranted() {

app/src/main/java/io/github/muntashirakon/AppManager/permission/PermUtils.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,25 @@
1010
import static io.github.muntashirakon.AppManager.compat.PermissionCompat.FLAG_PERMISSION_USER_FIXED;
1111
import static io.github.muntashirakon.AppManager.compat.PermissionCompat.FLAG_PERMISSION_USER_SET;
1212

13+
import android.Manifest;
1314
import android.annotation.UserIdInt;
1415
import android.app.AppOpsManager;
16+
import android.content.Intent;
1517
import android.content.pm.ApplicationInfo;
1618
import android.content.pm.PackageInfo;
1719
import android.os.Build;
1820
import android.os.RemoteException;
1921
import android.os.UserHandleHidden;
22+
import android.provider.Settings;
2023

2124
import androidx.annotation.NonNull;
2225
import androidx.annotation.Nullable;
2326
import androidx.annotation.RequiresPermission;
2427
import androidx.annotation.WorkerThread;
2528

29+
import java.util.HashMap;
30+
import java.util.Map;
31+
2632
import io.github.muntashirakon.AppManager.compat.ActivityManagerCompat;
2733
import io.github.muntashirakon.AppManager.compat.AppOpsManagerCompat;
2834
import io.github.muntashirakon.AppManager.compat.ManifestCompat;
@@ -31,10 +37,71 @@
3137
import io.github.muntashirakon.AppManager.self.SelfPermissions;
3238
import io.github.muntashirakon.AppManager.utils.BroadcastUtils;
3339
import io.github.muntashirakon.AppManager.utils.ContextUtils;
40+
import io.github.muntashirakon.AppManager.utils.IntentUtils;
3441

3542
public class PermUtils {
3643
private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
3744

45+
public static class SettingItem {
46+
public final String action;
47+
public final boolean supportPkg;
48+
49+
public SettingItem(String action, boolean supportPkg) {
50+
this.action = action;
51+
this.supportPkg = supportPkg;
52+
}
53+
54+
public SettingItem(String action) {
55+
this(action, true);
56+
}
57+
58+
public Intent toIntent(String packageName) {
59+
return IntentUtils.getSettings(action, supportPkg ? packageName : null);
60+
}
61+
}
62+
63+
public static final Map<String, SettingItem> permissionNameToSettingItem = new HashMap<String, SettingItem>() {{
64+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
65+
put(Manifest.permission.ACCESS_NOTIFICATION_POLICY, new SettingItem(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS, false));
66+
put(Manifest.permission.PACKAGE_USAGE_STATS, new SettingItem(Settings.ACTION_USAGE_ACCESS_SETTINGS));
67+
put(Manifest.permission.SYSTEM_ALERT_WINDOW, new SettingItem(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
68+
put(Manifest.permission.WRITE_SETTINGS, new SettingItem(Settings.ACTION_MANAGE_WRITE_SETTINGS));
69+
}
70+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
71+
put(Manifest.permission.REQUEST_INSTALL_PACKAGES, new SettingItem(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES));
72+
}
73+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
74+
put(Manifest.permission.MANAGE_EXTERNAL_STORAGE, new SettingItem(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION));
75+
// put(Manifest.permission.SYSTEM_ALERT_WINDOW, new SettingItem("android.settings.MANAGE_APP_OVERLAY_PERMISSION"));
76+
}
77+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
78+
put(Manifest.permission.MANAGE_MEDIA, new SettingItem(Settings.ACTION_REQUEST_MANAGE_MEDIA));
79+
put(Manifest.permission.SCHEDULE_EXACT_ALARM, new SettingItem(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM));
80+
}
81+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
82+
put(Manifest.permission.POST_NOTIFICATIONS, new SettingItem(Settings.ACTION_APP_NOTIFICATION_SETTINGS)); // android.provider.extra.APP_PACKAGE
83+
}
84+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
85+
put(Manifest.permission.RUN_USER_INITIATED_JOBS, new SettingItem("android.settings.MANAGE_APP_LONG_RUNNING_JOBS"));
86+
put(Manifest.permission.USE_FULL_SCREEN_INTENT, new SettingItem(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT));
87+
}
88+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
89+
put(Manifest.permission.MEDIA_ROUTING_CONTROL, new SettingItem(Settings.ACTION_REQUEST_MEDIA_ROUTING_CONTROL));
90+
}
91+
// Bound permissions
92+
put(Manifest.permission.BIND_ACCESSIBILITY_SERVICE, new SettingItem(Settings.ACTION_ACCESSIBILITY_SETTINGS, false));
93+
put(Manifest.permission.BIND_INPUT_METHOD, new SettingItem(Settings.ACTION_INPUT_METHOD_SETTINGS, false));
94+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
95+
put(Manifest.permission.BIND_AUTOFILL_SERVICE, new SettingItem(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE));
96+
}
97+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
98+
put(Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE, new SettingItem(Settings.ACTION_CREDENTIAL_PROVIDER));
99+
}
100+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
101+
put(Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE, new SettingItem(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS, false));
102+
}
103+
}};
104+
38105
/**
39106
* Grant the permission.
40107
*

app/src/main/java/io/github/muntashirakon/AppManager/utils/IntentUtils.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,55 @@
22

33
package io.github.muntashirakon.AppManager.utils;
44

5+
import android.annotation.SuppressLint;
56
import android.content.Intent;
67
import android.net.Uri;
8+
import android.os.Build;
79
import android.provider.Settings;
810

911
import androidx.annotation.NonNull;
12+
import androidx.annotation.Nullable;
13+
import androidx.annotation.RequiresApi;
1014

1115
public final class IntentUtils {
1216
@NonNull
1317
public static Intent getAppDetailsSettings(@NonNull String packageName) {
1418
return getSettings(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageName);
1519
}
1620

21+
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) // Added in r20
1722
@NonNull
18-
public static Intent getSettings(@NonNull String action, String packageName) {
19-
return new Intent(action)
23+
public static Intent getAppStorageSettings(@NonNull String packageName) {
24+
return getSettings("com.android.settings.APP_STORAGE_SETTINGS", packageName);
25+
}
26+
27+
@RequiresApi(Build.VERSION_CODES.N)
28+
@NonNull
29+
public static Intent getNetPolicySettings(@NonNull String packageName) {
30+
return getSettings(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS, packageName);
31+
}
32+
33+
@SuppressLint("BatteryLife")
34+
@RequiresApi(Build.VERSION_CODES.M)
35+
@NonNull
36+
public static Intent getBatteryOptSettings(@NonNull String packageName) {
37+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
38+
return getSettings("android.settings.VIEW_ADVANCED_POWER_USAGE_DETAIL", packageName);
39+
}
40+
return getSettings(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS, null);
41+
}
42+
43+
@NonNull
44+
public static Intent getSettings(@NonNull String action, @Nullable String packageName) {
45+
Intent intent = new Intent(action)
2046
.addCategory(Intent.CATEGORY_DEFAULT)
21-
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
22-
.setData(Uri.parse("package:" + packageName));
47+
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
48+
if (packageName != null) {
49+
if (action.equals(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
50+
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
51+
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
52+
} else intent.setData(Uri.parse("package:" + packageName));
53+
}
54+
return intent;
2355
}
2456
}

app/src/main/res/layout/item_app_details_perm.xml

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<!-- SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-or-later -->
22
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools"
43
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
55
style="@style/Widget.AppTheme.CardView.ListItem.Outlined"
66
android:layout_width="match_parent"
77
android:layout_height="wrap_content"
8-
android:focusable="true"
9-
app:cardElevation="0dp"
108
android:layout_marginHorizontal="?attr/listItemMarginHorizontal"
11-
android:layout_marginVertical="?attr/listItemMarginVertical">
9+
android:layout_marginVertical="?attr/listItemMarginVertical"
10+
android:focusable="true"
11+
app:cardElevation="0dp">
1212

1313
<androidx.appcompat.widget.LinearLayoutCompat
1414
android:layout_width="match_parent"
@@ -27,8 +27,8 @@
2727
android:id="@+id/perm_name"
2828
android:layout_width="match_parent"
2929
android:layout_height="wrap_content"
30-
android:textIsSelectable="true"
3130
android:textAppearance="?android:attr/textAppearanceListItem"
31+
android:textIsSelectable="true"
3232
tools:text="android.permission.A_VERY_LONG_PERMISSION_NAME" />
3333

3434
<androidx.appcompat.widget.AppCompatTextView
@@ -44,35 +44,56 @@
4444
android:layout_height="wrap_content"
4545
android:textAppearance="?attr/textAppearanceBodySmall"
4646
android:textSize="10sp"
47-
tools:text="⚑ dangerous|instant"
48-
tools:ignore="SmallSp" />
47+
tools:ignore="SmallSp"
48+
tools:text="⚑ dangerous|instant" />
4949

5050
<androidx.appcompat.widget.AppCompatTextView
5151
android:id="@+id/perm_package_name"
5252
android:layout_width="match_parent"
5353
android:layout_height="wrap_content"
54-
android:textIsSelectable="true"
5554
android:textAppearance="?attr/textAppearanceBodySmall"
55+
android:textIsSelectable="true"
5656
tools:text="Package Name: android" />
5757

5858
<androidx.appcompat.widget.AppCompatTextView
5959
android:id="@+id/perm_group"
6060
android:layout_width="match_parent"
6161
android:layout_height="wrap_content"
62-
android:textIsSelectable="true"
6362
android:textAppearance="?attr/textAppearanceBodySmall"
63+
android:textIsSelectable="true"
6464
tools:text="Group: android.permission-group.UNDEFINED" />
6565

6666
</androidx.appcompat.widget.LinearLayoutCompat>
6767

68-
<com.google.android.material.materialswitch.MaterialSwitch
69-
android:id="@+id/perm_toggle_btn"
70-
android:layout_width="56dp"
68+
<androidx.appcompat.widget.LinearLayoutCompat
69+
android:layout_width="wrap_content"
7170
android:layout_height="wrap_content"
7271
android:layout_gravity="center"
7372
android:layout_weight="0"
74-
android:clickable="false"
75-
android:focusable="false" />
73+
android:orientation="vertical">
74+
75+
<com.google.android.material.materialswitch.MaterialSwitch
76+
android:id="@+id/perm_toggle_btn"
77+
android:layout_width="56dp"
78+
android:layout_height="wrap_content"
79+
android:clickable="false"
80+
android:focusable="false"
81+
android:paddingVertical="@dimen/padding_medium" />
82+
83+
<com.google.android.material.button.MaterialButton
84+
android:id="@+id/action_settings"
85+
style="@style/Widget.AppTheme.Button.IconButton"
86+
android:layout_width="48dp"
87+
android:layout_height="48dp"
88+
android:layout_gravity="center"
89+
android:contentDescription="@string/view_in_settings"
90+
android:paddingVertical="@dimen/padding_small"
91+
android:visibility="gone"
92+
app:icon="@drawable/ic_settings"
93+
app:iconSize="20dp"
94+
tools:visibility="visible" />
95+
96+
</androidx.appcompat.widget.LinearLayoutCompat>
7697

7798
</androidx.appcompat.widget.LinearLayoutCompat>
7899

0 commit comments

Comments
 (0)