...
 
Commits (44)
......@@ -5,22 +5,23 @@ Yet another android package manager and viewer but...
- It is free and open source
- It has material design (and a nice UI)
- It doesn't have any useless permissions
- It doesn't connect to the internet
- It doesn't connect to the internet (internet permission is required for ADB mode)
- It tries to display as much information as possible in the main window
- It lists activities, broadcast receivers, services, providers, permissions, signatures, shared libraries, etc. of any app
- It can launch (exportable) activities, create (customizable) shortcuts
- It can block any activities, broadcast receivers, services or providers you like with Watt and Blocker import support (requires root)
- It can revoke permissions that are considered dangerous (requires root)
- It can disable app ops that are considered dangerous (requires root)
- It can revoke permissions that are considered dangerous (requires root/ADB)
- It can disable app ops that are considered dangerous (requires root/ADB)
- It can scan for trackers in apps and list (all or only) tracking classes (and their code dump)
- It can generate dynamic manifest for any app
- It can be used to view/edit/delete shared preferences of any app (requires root)
- It displays running processes/apps (requires root)
- It displays running processes/apps (requires root/ADB)
- It displays your app usage, data usage and app storage info (requires “Usage Access” permission)
- Apk files can be shared (hence the use of a provider)
- It can be used to clear app data or app cache (requires root)
- It can be used to clear app data or app cache (requires root/ADB)
- Batch operations: clear app data, disable run in background, disable/kill/uninstall apps
...and other minor features such as uninstalling/enabling/disabling apps, displaying app installation info, opening on F-Droid or Aurora Droid.
...and other minor features such as uninstalling/enabling/disabling apps, displaying app installation info, opening on F-Droid, Aurora Droid or Aurora Store.
It basically combined the features of five or six apps that any tech-savy person is needed to install in order to have a “life”.
......
......@@ -54,4 +54,5 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.jaredrummler:android-shell:1.0.0'
implementation 'com.tananaev:adblib:1.2'
}
......@@ -3,8 +3,10 @@
xmlns:tools="http://schemas.android.com/tools"
package="io.github.muntashirakon.AppManager"
android:installLocation="auto">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"
android:maxSdkVersion="25" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
......@@ -13,6 +15,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:name=".AppManager"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
<activity
......
package io.github.muntashirakon.AppManager;
import android.app.Application;
import android.content.Context;
public class AppManager extends Application {
private static AppManager instance;
public static AppManager getInstance() {
return instance;
}
public static Context getContext(){
return instance;
}
@Override
public void onCreate() {
instance = this;
super.onCreate();
}
}
......@@ -58,8 +58,9 @@ public class MainLoader extends AsyncTaskLoader<List<ApplicationItem>> {
item.size = (long) -1 * applicationInfo.targetSdkVersion;
}
if (isRootEnabled) {
item.blockedCount = ComponentsBlocker.getInstance(getContext(), pName, true)
.componentCount();
try (ComponentsBlocker cb = ComponentsBlocker.getInstance(getContext(), pName, true)) {
item.blockedCount = cb.componentCount();
}
}
itemList.add(item);
} catch (PackageManager.NameNotFoundException ignored) {}
......@@ -86,8 +87,9 @@ public class MainLoader extends AsyncTaskLoader<List<ApplicationItem>> {
item.sha = new Tuple<>("?", "?");
}
if (isRootEnabled) {
item.blockedCount = ComponentsBlocker.getInstance(getContext(),
applicationInfo.packageName, true).componentCount();
try (ComponentsBlocker cb = ComponentsBlocker.getInstance(getContext(), applicationInfo.packageName, true)) {
item.blockedCount = cb.componentCount();
}
}
itemList.add(item);
}
......
......@@ -12,7 +12,6 @@ import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.format.Formatter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
......@@ -27,6 +26,9 @@ import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import com.google.android.material.progressindicator.ProgressIndicator;
import com.google.android.material.textview.MaterialTextView;
import java.lang.ref.WeakReference;
import java.text.Collator;
import java.text.DateFormat;
......@@ -42,10 +44,12 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.fragments.AppUsageDetailsDialogFragment;
import io.github.muntashirakon.AppManager.usage.AppUsageStatsManager;
import io.github.muntashirakon.AppManager.usage.Utils.IntervalType;
import io.github.muntashirakon.AppManager.utils.Tuple;
import io.github.muntashirakon.AppManager.utils.Utils;
import static io.github.muntashirakon.AppManager.usage.Utils.USAGE_LAST_BOOT;
......@@ -53,7 +57,7 @@ import static io.github.muntashirakon.AppManager.usage.Utils.USAGE_TODAY;
import static io.github.muntashirakon.AppManager.usage.Utils.USAGE_WEEKLY;
import static io.github.muntashirakon.AppManager.usage.Utils.USAGE_YESTERDAY;
public class AppUsageActivity extends AppCompatActivity implements ListView.OnItemClickListener {
public class AppUsageActivity extends AppCompatActivity implements ListView.OnItemClickListener, SwipeRefreshLayout.OnRefreshListener {
@IntDef(value = {
SORT_BY_APP_LABEL,
SORT_BY_LAST_USED,
......@@ -75,9 +79,11 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
R.id.action_sort_by_mobile_data, R.id.action_sort_by_package_name,
R.id.action_sort_by_screen_time, R.id.action_sort_by_times_opened};
private ProgressIndicator mProgressIndicator;
private SwipeRefreshLayout mSwipeRefresh;
private AppUsageAdapter mAppUsageAdapter;
List<AppUsageStatsManager.PackageUS> mPackageUSList;
private long totalScreenTime;
private static long totalScreenTime;
private @IntervalType int current_interval = USAGE_TODAY;
private @SortOrder int mSortBy;
......@@ -92,6 +98,8 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
actionBar.setTitle(getString(R.string.app_usage));
}
mProgressIndicator = findViewById(R.id.progress_linear);
// Get usage stats
mAppUsageAdapter = new AppUsageAdapter(this);
ListView listView = findViewById(android.R.id.list);
......@@ -100,6 +108,12 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
listView.setAdapter(mAppUsageAdapter);
listView.setOnItemClickListener(this);
mSwipeRefresh = findViewById(R.id.swipe_refresh);
mSwipeRefresh.setColorSchemeColors(Utils.getThemeColor(this, android.R.attr.colorAccent));
mSwipeRefresh.setProgressBackgroundColorSchemeColor(Utils.getThemeColor(this, android.R.attr.colorPrimary));
mSwipeRefresh.setOnRefreshListener(this);
mSwipeRefresh.setOnChildScrollUpCallback((parent, child) -> listView.canScrollVertically(-1));
@SuppressLint("InflateParams")
View header = getLayoutInflater().inflate(R.layout.header_app_usage, null);
listView.addHeaderView(header);
......@@ -130,6 +144,14 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
else getAppUsage();
}
@Override
public void onRefresh() {
mSwipeRefresh.setRefreshing(false);
// Check permission
if (!Utils.checkUsageStatsPermission(this)) promptForUsageStatsPermission();
else getAppUsage();
}
@SuppressLint("RestrictedApi")
@Override
public boolean onCreateOptionsMenu(Menu menu) {
......@@ -223,15 +245,22 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
}
private void getAppUsage() {
int _try = 5; // try to get usage stat 5 times
do {
mPackageUSList = AppUsageStatsManager.getInstance(this).getUsageStats(0, current_interval);
} while (0 != --_try && mPackageUSList.size() == 0);
mAppUsageAdapter.setDefaultList(mPackageUSList);
totalScreenTime = 0;
for(AppUsageStatsManager.PackageUS appItem: mPackageUSList) totalScreenTime += appItem.screenTime;
sortPackageUSList();
setUsageSummary();
mProgressIndicator.show();
new Thread(() -> {
int _try = 5; // try to get usage stat 5 times
do {
mPackageUSList = AppUsageStatsManager.getInstance(this).getUsageStats(0, current_interval);
} while (0 != --_try && mPackageUSList.size() == 0);
totalScreenTime = 0;
for (AppUsageStatsManager.PackageUS appItem : mPackageUSList)
totalScreenTime += appItem.screenTime;
sortPackageUSList();
runOnUiThread(() -> {
mAppUsageAdapter.setDefaultList(mPackageUSList);
setUsageSummary();
mProgressIndicator.hide();
});
}).start();
}
private void promptForUsageStatsPermission() {
......@@ -304,12 +333,14 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
static class ViewHolder {
ImageView appIcon;
TextView appLabel;
TextView packageName;
TextView lastUsageDate;
TextView mobileDataUsage;
TextView wifiDataUsage;
TextView screenTime;
MaterialTextView appLabel;
MaterialTextView packageName;
MaterialTextView lastUsageDate;
MaterialTextView mobileDataUsage;
MaterialTextView wifiDataUsage;
MaterialTextView screenTime;
MaterialTextView percentUsage;
ProgressIndicator usageIndicator;
IconAsyncTask iconLoader;
}
......@@ -353,12 +384,15 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
holder.mobileDataUsage = convertView.findViewById(R.id.data_usage);
holder.wifiDataUsage = convertView.findViewById(R.id.wifi_usage);
holder.screenTime = convertView.findViewById(R.id.screen_time);
holder.percentUsage = convertView.findViewById(R.id.percent_usage);
holder.usageIndicator = convertView.findViewById(R.id.progress_linear);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
if(holder.iconLoader != null) holder.iconLoader.cancel(true);
}
AppUsageStatsManager.PackageUS packageUS = mAdapterList.get(position);
final AppUsageStatsManager.PackageUS packageUS = mAdapterList.get(position);
final int percentUsage = (int) (packageUS.screenTime * 100f / totalScreenTime);
// Set label (or package name on failure)
try {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageUS.packageName, 0);
......@@ -387,11 +421,21 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
holder.screenTime.setText(screenTimesWithTimesOpened);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Set data usage
holder.mobileDataUsage.setText("M: \u2191 " + Formatter.formatFileSize(mActivity, packageUS.mobileData.getFirst())
+ " \u2193 " + Formatter.formatFileSize(mActivity, packageUS.mobileData.getSecond()));
holder.wifiDataUsage.setText("W: \u2191 " + Formatter.formatFileSize(mActivity, packageUS.wifiData.getFirst())
+ " \u2193 " + Formatter.formatFileSize(mActivity, packageUS.wifiData.getSecond()));
final Tuple<Long, Long> mobileData = packageUS.mobileData;
if (mobileData.getFirst() != 0 || mobileData.getSecond() != 0) {
holder.mobileDataUsage.setText("M: \u2191 " + Formatter.formatFileSize(mActivity, mobileData.getFirst())
+ " \u2193 " + Formatter.formatFileSize(mActivity, mobileData.getSecond()));
} else holder.mobileDataUsage.setText("");
final Tuple<Long, Long> wifiData = packageUS.wifiData;
if (wifiData.getFirst() != 0 || wifiData.getSecond() != 0) {
holder.wifiDataUsage.setText("W: \u2191 " + Formatter.formatFileSize(mActivity, wifiData.getFirst())
+ " \u2193 " + Formatter.formatFileSize(mActivity, wifiData.getSecond()));
} else holder.wifiDataUsage.setText("");
}
// Set usage percentage
holder.percentUsage.setText(String.format(Locale.ROOT, "%d%%", percentUsage));
holder.usageIndicator.setProgress(percentUsage);
return convertView;
}
......
......@@ -3,7 +3,6 @@ package io.github.muntashirakon.AppManager.activities;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
......@@ -23,10 +22,10 @@ import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.progressindicator.ProgressIndicator;
import com.google.classysharkandroid.dex.DexLoaderBuilder;
import com.google.classysharkandroid.reflector.ClassesNamesList;
import com.google.classysharkandroid.reflector.Reflector;
......@@ -82,7 +81,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
private String packageInfo = "";
private CharSequence mAppName;
private ActionBar mActionBar;
private ProgressBar mProgressBar;
private ProgressIndicator mProgressIndicator;
private static String mConstraint;
private String mPackageName;
......@@ -159,7 +158,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
mListView.setDividerHeight(0);
mListView.setEmptyView(findViewById(android.R.id.empty));
mProgressBar = findViewById(R.id.progress_horizontal);
mProgressIndicator = findViewById(R.id.progress_linear);
final Uri uriFromIntent = inIntent.getData();
classList = new ClassesNamesList();
......@@ -315,7 +314,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
.setView(showText)
.setIcon(R.drawable.ic_frost_classysharkexodus_black_24dp)
.setNegativeButton(android.R.string.ok, null)
.setNeutralButton(R.string.exodus_link, (DialogInterface.OnClickListener) (dialog, which) -> {
.setNeutralButton(R.string.exodus_link, (dialog, which) -> {
Uri exodus_link = Uri.parse(String.format("https://reports.exodus-privacy.eu.org/en/reports/%s/latest/", mPackageName));
Intent intent = new Intent(Intent.ACTION_VIEW, exodus_link);
if (intent.resolveActivity(getPackageManager()) != null) {
......@@ -396,7 +395,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
}
// mListView.setAdapter(mAdapter);
mListView.setAdapter(mClassListingAdapter);
mProgressBar.setVisibility(View.GONE);
mProgressIndicator.hide();
if (classList.getClassNames().isEmpty() && totalClassesScanned == 0) {
// FIXME: Add support for odex (using root)
Toast.makeText(ClassListingActivity.this, R.string.system_odex_not_supported, Toast.LENGTH_LONG).show();
......
......@@ -11,10 +11,10 @@ import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.android.material.progressindicator.ProgressIndicator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -62,7 +62,7 @@ public class ClassViewerActivity extends AppCompatActivity {
("\\b[A-Z][A-Za-z0-9_]+\\b", Pattern.MULTILINE);
private String classDump;
private ProgressBar mProgressBar;
private ProgressIndicator mProgressIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
......@@ -72,7 +72,7 @@ public class ClassViewerActivity extends AppCompatActivity {
else
setContentView(R.layout.activity_any_viewer);
mProgressBar = findViewById(R.id.progress_horizontal);
mProgressIndicator = findViewById(R.id.progress_linear);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
......@@ -91,7 +91,7 @@ public class ClassViewerActivity extends AppCompatActivity {
}
private void displayContent() {
showProgressBar(true);
mProgressIndicator.show();
final TextView textView = findViewById(R.id.any_view);
final int typeClassColor = ContextCompat.getColor(this, R.color.ocean_blue);
final int keywordsColor = ContextCompat.getColor(this, R.color.dark_orange);
......@@ -120,15 +120,11 @@ public class ClassViewerActivity extends AppCompatActivity {
}
runOnUiThread(() -> {
textView.setText(spannableString);
ClassViewerActivity.this.showProgressBar(false);
mProgressIndicator.hide();
});
}).start();
}
private void showProgressBar(boolean show) {
mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
}
@SuppressLint("RestrictedApi")
@Override
public boolean onCreateOptionsMenu(Menu menu) {
......
......@@ -11,11 +11,11 @@ import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.progressindicator.ProgressIndicator;
import java.lang.ref.WeakReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -45,7 +45,7 @@ public class ManifestViewerActivity extends AppCompatActivity {
Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
private static String code;
private ProgressBar mProgressBar;
private ProgressIndicator mProgressIndicator;
@Override
......@@ -57,7 +57,7 @@ public class ManifestViewerActivity extends AppCompatActivity {
setContentView(R.layout.activity_any_viewer);
setSupportActionBar(findViewById(R.id.toolbar));
mProgressBar = findViewById(R.id.progress_horizontal);
mProgressIndicator = findViewById(R.id.progress_linear);
String packageName = getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
String filePath = null, applicationLabel = null;
......@@ -120,7 +120,7 @@ public class ManifestViewerActivity extends AppCompatActivity {
}
runOnUiThread(() -> {
textView.setText(spannableString);
ManifestViewerActivity.this.showProgressBar(false);
ManifestViewerActivity.this.showProgressIndicator(false);
});
}).start();
}
......@@ -130,8 +130,9 @@ public class ManifestViewerActivity extends AppCompatActivity {
finish();
}
private void showProgressBar(boolean show) {
mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
private void showProgressIndicator(boolean show) {
if (show) mProgressIndicator.show();
else mProgressIndicator.hide();
}
/**
......@@ -150,7 +151,7 @@ public class ManifestViewerActivity extends AppCompatActivity {
@Override
protected void onPreExecute() {
super.onPreExecute();
if(mActivity.get() != null) mActivity.get().showProgressBar(true);
if(mActivity.get() != null) mActivity.get().showProgressIndicator(true);
}
@Override
......@@ -189,7 +190,7 @@ public class ManifestViewerActivity extends AppCompatActivity {
// @Override
// protected void onPreExecute() {
// super.onPreExecute();
// if (mActivity.get() != null) mActivity.get().showProgressBar(true);
// if (mActivity.get() != null) mActivity.get().showProgressIndicator(true);
// }
//
// @Override
......@@ -223,7 +224,7 @@ public class ManifestViewerActivity extends AppCompatActivity {
// protected void onPostExecute(Boolean result) {
// super.onPostExecute(result);
// if (mActivity.get() != null) {
// mActivity.get().showProgressBar(false);
// mActivity.get().showProgressIndicator(false);
// if (result)
// mActivity.get().displayContent();
// else
......
......@@ -21,6 +21,7 @@ public class SettingsActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
setSupportActionBar(findViewById(R.id.toolbar));
findViewById(R.id.progress_linear).setVisibility(View.GONE);
appPref = AppPref.getInstance(this);
final SwitchMaterial rootSwitcher = findViewById(R.id.root_toggle_btn);
......
......@@ -16,11 +16,11 @@ import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.progressindicator.ProgressIndicator;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
......@@ -65,7 +65,7 @@ public class SharedPrefsActivity extends AppCompatActivity implements
private String mSharedPrefFile;
private File mTempSharedPrefFile;
private SharedPrefsListingAdapter mAdapter;
private ProgressBar mProgressBar;
private ProgressIndicator mProgressIndicator;
private HashMap<String, Object> mSharedPrefMap;
private static String mConstraint;
......@@ -107,8 +107,8 @@ public class SharedPrefsActivity extends AppCompatActivity implements
layoutParams.gravity = Gravity.END;
actionBar.setCustomView(searchView, layoutParams);
}
mProgressBar = findViewById(R.id.progress_horizontal);
mProgressBar.setVisibility(View.VISIBLE);
mProgressIndicator = findViewById(R.id.progress_linear);
mProgressIndicator.show();
ListView listView = findViewById(android.R.id.list);
listView.setTextFilterEnabled(true);
listView.setDividerHeight(0);
......@@ -289,7 +289,7 @@ public class SharedPrefsActivity extends AppCompatActivity implements
mSharedPrefMap = readSharedPref(mTempSharedPrefFile);
runOnUiThread(() -> {
mAdapter.setDefaultList(mSharedPrefMap);
mProgressBar.setVisibility(View.GONE);
mProgressIndicator.hide();
});
}
}
......
package io.github.muntashirakon.AppManager.adb;
import android.content.Context;
import android.util.Base64;
import com.tananaev.adblib.AdbBase64;
import com.tananaev.adblib.AdbConnection;
import com.tananaev.adblib.AdbCrypto;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import androidx.annotation.NonNull;
public class AdbConnectionManager {
public static final String TAG = "AdbConnMan";
@NonNull
private static AdbBase64 getAdbBase64() {
return data -> Base64.encodeToString(data, Base64.DEFAULT);
}
@NonNull
private static AdbCrypto setupCrypto(String publicKey, String privateKey)
throws NoSuchAlgorithmException, IOException {
File publicKeyFile = new File(publicKey);
File privateKeyFile = new File(privateKey);
AdbCrypto adbCrypto = null;
// Try to load a key pair from the files
if (publicKeyFile.exists() && privateKeyFile.exists()) {
try {
adbCrypto = AdbCrypto.loadAdbKeyPair(getAdbBase64(), privateKeyFile, publicKeyFile);
} catch (IOException e) {
// Failed to read from file
} catch (InvalidKeySpecException e) {
// Key spec was invalid
} catch (NoSuchAlgorithmException e) {
// RSA algorithm was unsupported with the crypo packages available
}
}
if (adbCrypto == null) {
// We couldn't load a key, so let's generate a new one
adbCrypto = AdbCrypto.generateAdbKeyPair(getAdbBase64());
// Save it
adbCrypto.saveAdbKeyPair(privateKeyFile, publicKeyFile);
}
return adbCrypto;
}
@NonNull
public static AdbConnection connect(@NonNull Context context, String host, int port)
throws IOException, NoSuchAlgorithmException, InterruptedException {
// Setup the crypto object required for the AdbConnection
String path = context.getCacheDir().getAbsolutePath();
String publicKey = path + File.separatorChar + "pub.key";
String privateKey = path + File.separatorChar + "priv.key";
AdbCrypto crypto = setupCrypto(publicKey, privateKey);
// Connect the socket to the remote host
Socket sock = new Socket(host, port);
// Construct the AdbConnection object
AdbConnection adbConnection = AdbConnection.create(sock, crypto);
adbConnection.connect();
return adbConnection;
}
}
package io.github.muntashirakon.AppManager.adb;
import android.text.TextUtils;
import android.util.Log;
import com.tananaev.adblib.AdbConnection;
import com.tananaev.adblib.AdbStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import androidx.annotation.NonNull;
import io.github.muntashirakon.AppManager.AppManager;
import io.github.muntashirakon.AppManager.utils.Utils;
public class AdbShell {
private static AdbConnection adbConnection;
private static AdbStream shell;
private static final String adbHost = "127.0.0.1";
private static final int adbPort = 5555;
private static final File cachePath = Objects.requireNonNull(AppManager.getContext().getExternalCacheDir());
public static final File stdoutPath = new File(cachePath, ".stdout");
public static final File cmdPath = new File(cachePath, ".cmd");
public static final File retCodePath = new File(cachePath, ".code");
private static final String formattedCmdString = "#!/bin/sh\n%s\necho $? > " + retCodePath.getAbsolutePath();
public static class CommandResult {
public int returnCode;
public @NonNull List<String> stdout;
private CommandResult(@NonNull List<String> stdout, int returnCode) {
this.returnCode = returnCode;
this.stdout = stdout;
}
public boolean isSuccessful() {
return returnCode == 0;
}
public String getStdout() {
return TextUtils.join("\n", stdout);
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@NonNull
synchronized public static CommandResult run(String command)
throws IOException, InterruptedException, NoSuchAlgorithmException {
if (adbConnection == null) {
adbConnection = AdbConnectionManager.connect(AppManager.getContext(), adbHost, adbPort);
}
if (shell != null) shell.close();
shell = adbConnection.open("shell:");
if (!cachePath.exists()) cachePath.mkdir();
// Write command
try (FileOutputStream cmdStream = new FileOutputStream(cmdPath)) {
cmdStream.write(String.format(formattedCmdString, command).getBytes());
}
// Execute command
shell.write((" /bin/sh " + cmdPath.getAbsolutePath() + " | awk '{ print $0 }' > " + stdoutPath.getAbsolutePath() + "; exit\n").getBytes(), true);
// Debug output
while (!shell.isClosed()) {
try {
Log.d("AdbShell - Raw", new String(shell.read(), StandardCharsets.US_ASCII));
} catch (Exception e) {
break;
}
}
// Wait for result
while (!retCodePath.exists()) Thread.sleep(500);
while (!stdoutPath.exists()) Thread.sleep(500);
// Read exit code
final String retCode = Utils.getFileContent(retCodePath);
int returnCode = -1;
try {
returnCode = Integer.parseInt(retCode);
} catch (Exception ignored) {}
// Read standard output
List<String> stdout = new ArrayList<>();
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(stdoutPath))) {
String line;
while ((line = bufferedReader.readLine()) != null) stdout.add(line.trim());
} catch (Exception e) {
e.printStackTrace();
}
// Debug logs
Log.d("AdbShell - Command", command);
Log.d("AdbShell - Stdout", stdout.toString());
Log.d("AdbShell - Return Code", returnCode + " (" + retCode + ")");
// Delete files
stdoutPath.delete();
retCodePath.delete();
return new CommandResult(stdout, returnCode);
}
}
......@@ -1799,6 +1799,7 @@ public class AppOpsManager {
public static int strOpToOp(@NonNull String op) {
Integer val = sOpStrToOp.get(op);
if (val == null) {
// TODO: Try old names
throw new IllegalArgumentException("Unknown operation string: " + op);
}
return val;
......@@ -1911,19 +1912,21 @@ public class AppOpsManager {
*/
public static final class OpEntry implements Parcelable {
private final int mOp;
private final String mOpStr;
private final Boolean mRunning;
private final @Mode int mMode;
private final @NonNull String mMode;
private final long mAccessTime;
private final long mRejectTime;
private final long mDuration;
private final @Nullable String mProxyUid;
private final @Nullable String mProxyPackageName;
public OpEntry(int op, boolean running, @Mode int mode,
public OpEntry(int op, @NonNull String opStr, boolean running, @NonNull String mode,
long accessTime, long rejectTime,
long duration, @Nullable String proxyUid,
@Nullable String proxyPackageName) {
mOp = op;
mOpStr = opStr;
mRunning = running;
mMode = mode;
mAccessTime = accessTime;
......@@ -1933,8 +1936,9 @@ public class AppOpsManager {
mProxyPackageName = proxyPackageName;
}
public OpEntry(int op, @Mode int mode) {
public OpEntry(int op, @NonNull String mode) {
mOp = op;
mOpStr = "";
mMode = mode;
mRunning = false;
mAccessTime = 0;
......@@ -1953,14 +1957,13 @@ public class AppOpsManager {
*/
public @NonNull
String getOpStr() {
return sOpToString[mOp];
return mOpStr;
}
/**
* @return this entry's current mode, such as {@link #MODE_ALLOWED}.
* @return this entry's current mode string value, such as allow and ignore.
*/
public @Mode
int getMode() {
public String getMode() {
return mMode;
}
......@@ -2012,9 +2015,10 @@ public class AppOpsManager {
@Override
public String toString() {
return "OpEntry{" +
"mOp=" + opToName(mOp) +
"mOp=" + mOp +
", mOpStr=" + mOpStr +
", mRunning=" + mRunning +
", mMode=" + modeToName(mMode) +
", mMode=" + mMode +
", mAccessTime=" + mAccessTime +
", mRejectTime=" + mRejectTime +
", mDuration=" + mDuration +
......@@ -2029,7 +2033,8 @@ public class AppOpsManager {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mOp);
dest.writeInt(mMode);
dest.writeString(mOpStr);
dest.writeString(mMode);
dest.writeValue(mRunning);
dest.writeLong(mAccessTime);
dest.writeLong(mRejectTime);
......@@ -2040,7 +2045,8 @@ public class AppOpsManager {
OpEntry(@NonNull Parcel source) {
mOp = source.readInt();
mMode = source.readInt();
mOpStr = source.readString();
mMode = Objects.requireNonNull(source.readString());
mRunning = (Boolean) source.readValue(getClass().getClassLoader());
mAccessTime = source.readLong();
mRejectTime = source.readLong();
......
......@@ -15,7 +15,7 @@ import io.github.muntashirakon.AppManager.runner.Runner;
@SuppressLint("DefaultLocale")
public
class AppOpsService implements IAppOpsService {
private static final Pattern OP_MATCHER = Pattern.compile("(?:Uid mode: )?(\\w+): (\\w+)" +
private static final Pattern OP_MATCHER = Pattern.compile("(?:Uid mode: )?([\\w()]+): ([\\w=]+)" +
"(?:; time=(?:\\s*0|([+\\-])(\\d+d)?(\\d{1,2}h)?(\\d{1,2}m)?(\\d{1,2}s)?(\\d{1,3}m))s ago)?" +
"(?:; rejectTime=(?:\\s*0|([+\\-])(\\d+d)?(\\d{1,2}h)?(\\d{1,2}m)?(\\d{1,2}s)?(\\d{1,3}m))s ago)?" +
"( \\(running\\))?(?:; duration=(?:\\s*0|([+\\-])(\\d+d)?(\\d{1,2}h)?(\\d{1,2}m)?(\\d{1,2}s)?(\\d{1,3}m))s)?");
......@@ -29,6 +29,9 @@ class AppOpsService implements IAppOpsService {
};
private static final int DEFAULT_MODE_SKIP = 14;
private static final int UNKNOWN_MODE_SKIP = 5;
private static final int UNKNOWN_OP_SKIP = 8;
private static final int OP_PREFIX_OP_SKIP = 3;
private boolean isSuccessful = false;
private List<String> output = null;
......@@ -50,24 +53,25 @@ class AppOpsService implements IAppOpsService {
* an invalid operation name or mode name or there's an error parsing the output
*/
@Override
@AppOpsManager.Mode
public int checkOperation(int op, int uid, @Nullable String packageName)
public String checkOperation(int op, int uid, @Nullable String packageName)
throws Exception {
String opStr = AppOpsManager.opToName(op);
if (uid >= 0)
runCommand(String.format("appops get %d %s", uid, opStr));
else if (packageName != null)
runCommand(String.format("appops get %s %s", packageName, opStr));
if (packageName != null)
runCommand(String.format("appops get %s %d", packageName, op));
else if (uid >= 0)
runCommand(String.format("appops get %d %d", uid, op));
else throw new Exception("No uid or package name provided");
if (isSuccessful) {
try {
String opModeOut;
if (output.size() == 1) {
AppOpsManager.OpEntry entry = parseOpName(output.get(0));
return entry.getMode();
return parseOpName(output.get(0)).getMode();
} else if (output.size() == 2) {
opModeOut = output.get(1).substring(DEFAULT_MODE_SKIP);
return strModeToMode(opModeOut);
String line2 = output.get(1);
if (line2.startsWith("Default mode:")) {
opModeOut = line2.substring(DEFAULT_MODE_SKIP);
return opModeOut;
} else return parseOpName(line2).getMode();
}
} catch (IndexOutOfBoundsException e) {
throw new Exception("Invalid output from appops");
......@@ -88,19 +92,24 @@ class AppOpsService implements IAppOpsService {
if (!isSuccessful) throw new Exception("Failed to get operations for package " + packageName);
} else {
for(int op: ops) {
runCommand(String.format("appops get %s %s", packageName, AppOpsManager.opToName(op)));
runCommand(String.format("appops get %s %d", packageName, op));
if (output.size() == 1) { // Trivial parser
lines.addAll(output);
} else if (output.size() == 2) { // Custom parser
String name = String.format("%s: %s", AppOpsManager.opToName(op), output.get(1).substring(DEFAULT_MODE_SKIP));
lines.add(name);
String line2 = output.get(1);
if (line2.startsWith("Default mode:")) {
String name = String.format("%s: %s", AppOpsManager.opToName(op), line2.substring(DEFAULT_MODE_SKIP));
lines.add(name);
} else lines.add(line2); // To prevent weird bug in some cases
}
// if (!isSuccessful) throw new Exception("Failed to get operations for package " + packageName);
}
}
List<AppOpsManager.OpEntry> opEntries = new ArrayList<>();
for(String line: lines) {
opEntries.add(parseOpName(line));
try {
opEntries.add(parseOpName(line));
} catch (Exception ignored) {}
}
AppOpsManager.PackageOps packageOps = new AppOpsManager.PackageOps(packageName, uid, opEntries);
packageOpsList.add(packageOps);
......@@ -109,15 +118,14 @@ class AppOpsService implements IAppOpsService {
@Override
public void setMode(int op, int uid, String packageName, int mode) throws Exception {
String opStr = AppOpsManager.opToName(op);
String modeStr = AppOpsManager.modeToName(mode);
if (uid >= 0)
runCommand(String.format("appops set --uid %d %s %s", uid, opStr, modeStr));
runCommand(String.format("appops set --uid %d %d %s", uid, op, modeStr));
else if (packageName != null)
runCommand(String.format("appops set %s %s %s", packageName, opStr, modeStr));
runCommand(String.format("appops set %s %d %s", packageName, op, modeStr));
else throw new Exception("No uid or package name provided");
if (isSuccessful) { return; }
throw new Exception("Failed to check operation " + opStr);
throw new Exception("Failed to check operation " + op);
}
@Override
......@@ -139,25 +147,6 @@ class AppOpsService implements IAppOpsService {
output = result.getOutputAsList();
}
/**
* Mode names to mode values
* @param modeStr Mode name, eg. allow
* @return Integer value of the mode
*/
@SuppressLint("WrongConstant")
@AppOpsManager.Mode
private static int strModeToMode(String modeStr) throws Exception {
for (int i = AppOpsManager.MODE_NAMES.length - 1; i >= 0; i--) {
if (AppOpsManager.MODE_NAMES[i].equals(modeStr)) {
return i;
}
}
try {
return Integer.parseInt(modeStr);
} catch (NumberFormatException ignored) {}
throw new Exception("Invalid mode " + modeStr);
}
/**
* String value of op to integer value of op
*
......@@ -178,18 +167,32 @@ class AppOpsService implements IAppOpsService {
}
}
private static AppOpsManager.OpEntry parseOpName(String line) throws Exception {
@NonNull
private static AppOpsManager.OpEntry parseOpName(@NonNull String line) throws Exception {
Matcher matcher = OP_MATCHER.matcher(line);
if (matcher.find()) {
if (matcher.group(1) == null && matcher.group(2) == null)
String opStr = matcher.group(1);
String modeStr = matcher.group(2);
if (opStr == null || modeStr == null)
throw new Exception("Op name or mode cannot be empty");
int op = strOpToOp(matcher.group(1));
@AppOpsManager.Mode int mode = strModeToMode(matcher.group(2));
// Handle Unknown(op)
if (opStr.startsWith("Unknown("))
opStr = opStr.substring(UNKNOWN_OP_SKIP, opStr.length()-1);
if (opStr.startsWith("OP_"))
opStr = opStr.substring(OP_PREFIX_OP_SKIP);
// FIXME: Check old opStr as well
// Handle mode=5
if (modeStr.startsWith("mode="))
modeStr = modeStr.substring(UNKNOWN_MODE_SKIP);
int op = AppOpsManager.OP_NONE;
try {
op = strOpToOp(opStr);
} catch (Exception ignore) {}
boolean running = matcher.group(15) != null;
long accessTime = getTime(matcher, 3);
long rejectTime = getTime(matcher, 9);
long duration = getTime(matcher, 16);
return new AppOpsManager.OpEntry(op, running, mode, accessTime, rejectTime, duration, null, null);
return new AppOpsManager.OpEntry(op, opStr, running, modeStr, accessTime, rejectTime, duration, null, null);
}
throw new Exception("Failed to parse line");
}
......
......@@ -3,8 +3,7 @@ package io.github.muntashirakon.AppManager.appops;
import java.util.List;
interface IAppOpsService {
@AppOpsManager.Mode
int checkOperation(int op, int uid, String packageName) throws Exception;
String checkOperation(int op, int uid, String packageName) throws Exception;
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) throws Exception;
void setMode(int op, int uid, String packageName, int mode) throws Exception;
void resetAllModes(int reqUserId, String reqPackageName) throws Exception;
......
package io.github.muntashirakon.AppManager.batchops;
import android.content.Context;
import android.text.TextUtils;
import java.util.List;
import java.util.Locale;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import io.github.muntashirakon.AppManager.appops.AppOpsManager;
import io.github.muntashirakon.AppManager.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.runner.Runner;
import io.github.muntashirakon.AppManager.storage.StorageManager;
public class BatchOpsManager {
@IntDef(value = {
OP_BACKUP_APK,
OP_BACKUP_DATA,
OP_CLEAR_DATA,
OP_DISABLE,
OP_DISABLE_BACKGROUND,
OP_EXPORT_RULES,
OP_KILL,
OP_UNINSTALL
})
public @interface OpType {}
public static final int OP_BACKUP_APK = 0;
public static final int OP_BACKUP_DATA = 1;
public static final int OP_CLEAR_DATA = 2;
public static final int OP_DISABLE = 3;
public static final int OP_DISABLE_BACKGROUND = 4;
public static final int OP_EXPORT_RULES = 5;
public static final int OP_KILL = 6;
public static final int OP_UNINSTALL = 7;
private Runner runner;
private Context context;
public BatchOpsManager(Context context) {
this.context = context;
this.runner = Runner.getInstance(context);
}
private List<String> packageNames;
private Result lastResult;
@NonNull
public Result performOp(@OpType int op, List<String> packageNames) {
this.runner.clear();
this.packageNames = packageNames;
switch (op) {
case OP_BACKUP_APK: // TODO
case OP_BACKUP_DATA: // TODO
break;
case OP_CLEAR_DATA: return opClearData();
case OP_DISABLE: return opDisable();
case OP_DISABLE_BACKGROUND: return opDisableBackground();
case OP_EXPORT_RULES: // TODO
break;
case OP_KILL: return opKill();
case OP_UNINSTALL: return opUninstall();
}
lastResult = new Result() {
@Override
public boolean isSuccessful() {
return false;
}
@Override
public List<String> failedPackages() {
return null;
}
};
return lastResult;
}
public Result getLastResult() {
return lastResult;
}
@NonNull
private Result opClearData() {
for(String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm clear %s", packageName));
}
return runOpAndFetchResults();
}
@NonNull
private Result opDisable() {
for(String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm disable %s", packageName));
}
return runOpAndFetchResults();
}
@NonNull
private Result opDisableBackground() {
for(String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "appops set %s 63 %d", packageName, AppOpsManager.MODE_IGNORED));
}
Result result = runOpAndFetchResults();
List<String> failedPackages = result.failedPackages();
for (String packageName: packageNames) {
if (!failedPackages.contains(packageName)) {
try (ComponentsBlocker cb = ComponentsBlocker.getMutableInstance(context, packageName)) {
cb.setAppOp(String.valueOf(AppOpsManager.OP_RUN_IN_BACKGROUND), AppOpsManager.MODE_IGNORED);
}
}
}
return result;
}
@NonNull
private Result opKill() {
for (String packageName : packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pidof %s", packageName), false);
}
Result result = runOpAndFetchResults();
List<String> pidOrPackageNames = result.failedPackages();
runner.clear();
for (int i = 0; i<packageNames.size(); ++i) {
if (!pidOrPackageNames.get(i).equals(packageNames.get(i))) {
addCommand(packageNames.get(i), String.format(Locale.ROOT, "kill -9 %s", pidOrPackageNames.get(i)));
}
}
return runOpAndFetchResults();
}
@NonNull
private Result opUninstall() {
for(String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm uninstall --user 0 %s", packageName));
}
return runOpAndFetchResults();
}
private void addCommand(String packageName, String command) {
addCommand(packageName, command, true);
}
private void addCommand(String packageName, String command, boolean isDevNull) {
runner.addCommand(String.format(Locale.ROOT, "%s %s || echo %s", command, isDevNull ? "> /dev/null 2>&1" : "", packageName));
}
@NonNull
private Result runOpAndFetchResults() {
Runner.Result result = runner.run();
lastResult = new Result() {
@Override
public boolean isSuccessful() {
return TextUtils.isEmpty(result.getOutput());
}
@Override
public List<String> failedPackages() {
return result.getOutputAsList();
}
};
return lastResult;
}
public interface Result {
boolean isSuccessful();
List<String> failedPackages();
}
}
......@@ -8,19 +8,16 @@ import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Build;
import android.os.FileUtils;
import com.google.classysharkandroid.utils.IOUtils;
import android.util.Xml;
import org.json.JSONArray;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
......@@ -55,8 +52,7 @@ public class ExternalComponentsImporter {
Integer failedCount = 0;
for(Uri uri: uriList) {
try {
String packageName = applyFromWatt(context, uri);
ComponentsBlocker.getInstance(context, packageName).applyRules(true);
applyFromWatt(context, uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
failed = true;
......@@ -69,28 +65,53 @@ public class ExternalComponentsImporter {
/**
* Watt only supports IFW, so copy them directly
*
* FIXME: Breaks in v2.5.6
* @param context Application context
* @param fileUri File URI
*/
@NonNull
private static String applyFromWatt(@NonNull Context context, Uri fileUri) throws FileNotFoundException {
private static void applyFromWatt(@NonNull Context context, Uri fileUri) throws FileNotFoundException {
String filename = Utils.getName(context.getContentResolver(), fileUri);
if (filename == null) throw new FileNotFoundException("The requested content is not found.");
File amFile = new File(ComponentsBlocker.provideLocalIfwRulesPath(context) + "/" + filename);
InputStream inputStream = context.getContentResolver().openInputStream(fileUri);
if (inputStream == null) throw new FileNotFoundException("The requested content is not found.");
OutputStream outputStream = new FileOutputStream(amFile);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
FileUtils.copy(inputStream, outputStream);
} else {
IOUtils.copy(inputStream, outputStream);
try (InputStream rulesStream = context.getContentResolver().openInputStream(fileUri)) {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(rulesStream, null);
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "rules");
int event = parser.nextTag();
StorageManager.Type componentType = StorageManager.Type.UNKNOWN;
String packageName = Utils.trimExtension(filename);
try (ComponentsBlocker cb = ComponentsBlocker.getMutableInstance(context, packageName)) {
String name;
while (!(name = parser.getName()).equals("rules")) {
switch (event) {
case XmlPullParser.START_TAG:
if (name.equals(ComponentsBlocker.TAG_ACTIVITY)
|| name.equals(ComponentsBlocker.TAG_RECEIVER)
|| name.equals(ComponentsBlocker.TAG_SERVICE)) {
componentType = cb.getComponentType(name);
}
break;
case XmlPullParser.END_TAG:
if (name.equals("component-filter")) {
String fullKey = parser.getAttributeValue(null, "name");
int divider = fullKey.indexOf('/');
String pkgName = fullKey.substring(0, divider);
String componentName = fullKey.substring(divider + 1);
if (pkgName.equals(packageName)) {
// Overwrite rules if exists
cb.addComponent(componentName, componentType);
}
}
}
event = parser.nextTag();
}
cb.applyRules(true);
}
}
inputStream.close();
outputStream.close();
return Utils.trimExtension(filename);
} catch (IOException e) {
} catch (IOException|XmlPullParserException e) {
throw new FileNotFoundException(e.getMessage());
}
}
......@@ -109,13 +130,19 @@ public class ExternalComponentsImporter {
PackageManager packageManager = context.getPackageManager();
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray components = jsonObject.getJSONArray("components");
for(int i = 0; i<components.length(); ++i) {
for (int i = 0; i<components.length(); ++i) {
JSONObject component = (JSONObject) components.get(i);
String packageName = component.getString("packageName");
if (!packageInfoList.containsKey(packageName))
if (!packageInfoList.containsKey(packageName)) {
int apiCompatFlags;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
apiCompatFlags = PackageManager.MATCH_DISABLED_COMPONENTS;
else apiCompatFlags = PackageManager.GET_DISABLED_COMPONENTS;
packageInfoList.put(packageName, packageManager.getPackageInfo(packageName,
PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS
| PackageManager.GET_PROVIDERS | PackageManager.GET_SERVICES));
| PackageManager.GET_PROVIDERS | PackageManager.GET_SERVICES
| apiCompatFlags));
}
String componentName = component.getString("name");
if (!packageComponents.containsKey(packageName))
packageComponents.put(packageName, new HashMap<>());
......@@ -123,17 +150,18 @@ public class ExternalComponentsImporter {
packageComponents.get(packageName).put(componentName, getType(componentName, packageInfoList.get(packageName)));
}
if (packageComponents.size() > 0) {
ComponentsBlocker blocker;
for (String packageName: packageComponents.keySet()) {
HashMap<String, StorageManager.Type> disabledComponents = packageComponents.get(packageName);
//noinspection ConstantConditions
if (disabledComponents.size() > 0) {
blocker = ComponentsBlocker.getInstance(context, packageName);
for (String component: disabledComponents.keySet()) {
blocker.addComponent(component, disabledComponents.get(component));
try (ComponentsBlocker cb = ComponentsBlocker.getMutableInstance(context, packageName)){
for (String component: disabledComponents.keySet()) {
cb.addComponent(component, disabledComponents.get(component));
}
cb.applyRules(true);
if (!cb.isRulesApplied())
throw new Exception("Rules not applied for package " + packageName);
}
blocker.applyRules(true);
if (!blocker.isRulesApplied()) throw new Exception("Rules not applied for package " + packageName);
}
}
}
......
package io.github.muntashirakon.AppManager.runner;
import android.content.Context;
import android.text.TextUtils;
import java.io.IOException;