...
 
Commits (45)
......@@ -53,4 +53,6 @@ dependencies {
implementation 'com.jaredrummler:android-shell:1.0.0'
implementation 'com.tananaev:adblib:1.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
}
/*
* Copyright 2015 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.classysharkandroid.reflector;
import java.util.LinkedList;
import java.util.List;
public class ClassesNamesList {
private List<String> list;
public ClassesNamesList() {
list = new LinkedList<>();
}
public void add(String className) {
list.add(className);
}
public int size() { return this.list.size(); }
public List<String> getClassNames() {
return this.list;
}
public String getClassName(int position) {
return this.list.get(position);
}
}
......@@ -89,16 +89,15 @@ public class Reflector {
if (x.lastIndexOf(".") != -1) {
y = x.substring(0, x.lastIndexOf("."));
words.add(new TaggedWord("\npackage ", TAG.MODIFIER));
words.add(new TaggedWord("package ", TAG.MODIFIER));
words.add(new TaggedWord(y, TAG.IDENTIFIER));
words.add(new TaggedWord(";", TAG.MODIFIER));
words.add(new TaggedWord(";\n", TAG.MODIFIER));
}
try {
fields = currentClass.getDeclaredFields(); // NoClassDefFoundError ccc71/at/xposed/blocks/at_block_manage_accounts$5
constructors = currentClass.getDeclaredConstructors();
methods = currentClass.getDeclaredMethods();
} catch (NoClassDefFoundError e){
e.printStackTrace();
return e.toString();
......@@ -181,14 +180,14 @@ public class Reflector {
words.add(new TaggedWord("\n{", TAG.IDENTIFIER));
words.add(new TaggedWord("\n" +
"/*\n" +
" * Field Definitions.\n" +
" */", TAG.DOCUMENT));
" /*\n" +
" * Field Definitions.\n" +
" */", TAG.DOCUMENT));
for (Field field : fields) {
int md = field.getModifiers();
words.add(new TaggedWord("\n " + Modifier.toString(md) + " ", TAG.MODIFIER));
words.add(new TaggedWord("\n " + Modifier.toString(md) + " ", TAG.MODIFIER));
words.add(new TaggedWord(ClassTypeAlgorithm.TypeName(field.getType().getName(), null) + " ",
TAG.IDENTIFIER));
words.add(new TaggedWord(field.getName() + ";", TAG.DOCUMENT));
......@@ -198,9 +197,9 @@ public class Reflector {
// http://stackoverflow.com/questions/140537/how-to-use-java-reflection-when-the-enum-type-is-a-class
words.add(new TaggedWord("\n" +
"/*\n" +
" * Declared Constructors.\n" +
" */\n", TAG.DOCUMENT));
" /*\n" +
" * Declared Constructors.\n" +
" */\n", TAG.DOCUMENT));
x = ClassTypeAlgorithm.TypeName(currentClass.getName(), null);
for (Constructor constructor : constructors) {
int md = constructor.getModifiers();
......
......@@ -21,11 +21,7 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
......@@ -47,21 +43,6 @@ import androidx.annotation.Nullable;
public class IOUtils {
public static void bytesToFile(byte[] bytes, File result) throws IOException {
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream(result));
bos.write(bytes);
bos.flush();
bos.close();
}
@NonNull
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
copy(input, output);
return output.toByteArray();
}
public static int copy(InputStream input, OutputStream output) throws IOException {
long count = copyLarge(input, output);
if (count > Integer.MAX_VALUE) {
......
......@@ -11,7 +11,7 @@ import android.os.Build;
import androidx.loader.content.AsyncTaskLoader;
import io.github.muntashirakon.AppManager.activities.MainActivity;
import io.github.muntashirakon.AppManager.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.types.ApplicationItem;
import io.github.muntashirakon.AppManager.utils.AppPref;
import io.github.muntashirakon.AppManager.utils.Tuple;
......
package io.github.muntashirakon.AppManager;
public class StaticDataset {
private static String[] trackerCodeSignatures;
private static String[] trackerNames;
public static String[] getTrackerCodeSignatures() {
if (trackerCodeSignatures == null) {
trackerCodeSignatures = AppManager.getContext().getResources().getStringArray(R.array.tracker_signatures);
}
return trackerCodeSignatures;
}
public static String[] getTrackerNames() {
if (trackerNames == null) {
trackerNames = AppManager.getContext().getResources().getStringArray(R.array.tracker_names);
}
return trackerNames;
}
}
......@@ -8,46 +8,43 @@ import android.os.Bundle;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.lifecycle.ViewModelProvider;
import androidx.viewpager.widget.ViewPager;
import io.github.muntashirakon.AppManager.AppManager;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.fragments.AppDetailsFragment;
import io.github.muntashirakon.AppManager.utils.Utils;
import io.github.muntashirakon.AppManager.viewmodels.AppDetailsViewModel;
public class AppDetailsActivity extends AppCompatActivity {
public static final String EXTRA_PACKAGE_NAME = "pkg";
public static String sConstraint;
public AppDetailsViewModel model;
public SearchView searchView;
private String mPackageName;
private TypedArray mTabTitleIds;
AppDetailsFragmentStateAdapter appDetailsFragmentStateAdapter;
ViewPager2 viewPager2;
AppDetailsFragment[] fragments;
private int pageNo;
private boolean fragmentLoaded = false;
private AppDetailsFragment[] fragments;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_details);
setSupportActionBar(findViewById(R.id.toolbar));
sConstraint = null;
mPackageName = getIntent().getStringExtra(AppInfoActivity.EXTRA_PACKAGE_NAME);
if (mPackageName == null) {
Toast.makeText(this, getString(R.string.empty_package_name), Toast.LENGTH_LONG).show();
......@@ -61,16 +58,18 @@ public class AppDetailsActivity extends AppCompatActivity {
finish();
return;
}
// Get model
model = ViewModelProvider.AndroidViewModelFactory.getInstance(AppManager.getInstance()).create(AppDetailsViewModel.class);
model.setPackageName(mPackageName);
// Initialize tabs
mTabTitleIds = getResources().obtainTypedArray(R.array.TAB_TITLES);
FragmentManager fragmentManager = getSupportFragmentManager();
appDetailsFragmentStateAdapter = new AppDetailsFragmentStateAdapter(fragmentManager, getLifecycle());
viewPager2 = findViewById(R.id.pager);
viewPager2.setAdapter(appDetailsFragmentStateAdapter);
ViewPager viewPager = findViewById(R.id.pager);
viewPager.setAdapter(new AppDetailsFragmentPagerAdapter(fragmentManager));
fragments = new AppDetailsFragment[mTabTitleIds.length()];
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
final SearchView searchView = new SearchView(actionBar.getThemedContext());
searchView = new SearchView(actionBar.getThemedContext());
actionBar.setDisplayShowCustomEnabled(true);
searchView.setQueryHint(getString(R.string.search));
......@@ -83,78 +82,9 @@ public class AppDetailsActivity extends AppCompatActivity {
ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.END;
actionBar.setCustomView(searchView, layoutParams);
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
/**
* Page selected is called in two ways: 1) Called before fragment when a tab is
* pressed directly, 2) Called after the fragment when dragging is used. In the
* first way, we cannot call reset() on the fragments adapter since it's not loaded,
* but we can do that for the second case.
* @param position Position of the view
*/
@Override
public void onPageSelected(@AppDetailsFragment.Property int position) {
super.onPageSelected(position);
pageNo = position;
if (fragments[position] != null) { // Fragment is created (the second case)
fragmentLoaded = true;
fixSearch();
} else fragmentLoaded = false;
}
/**
* Our interest is {@link ViewPager2#SCROLL_STATE_IDLE}. It's called in two ways in
* respect to {@link ViewPager2.OnPageChangeCallback#onPageSelected(int)}. We need
* to check whether the fragment adapter was refreshed in the function above. If it
* doesn't we'll refresh here.
* @param state Current scroll state
*/
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
switch (state) {
case ViewPager2.SCROLL_STATE_DRAGGING:
break;
case ViewPager2.SCROLL_STATE_IDLE:
if (!fragmentLoaded && fragments[pageNo] != null) {
fragmentLoaded = true;
fixSearch();
}
break;
case ViewPager2.SCROLL_STATE_SETTLING:
break;
}
}
private void fixSearch() {
switch (pageNo) {
case AppDetailsFragment.ACTIVITIES:
case AppDetailsFragment.SERVICES:
case AppDetailsFragment.RECEIVERS:
case AppDetailsFragment.PROVIDERS:
case AppDetailsFragment.APP_OPS:
case AppDetailsFragment.USES_PERMISSIONS:
case AppDetailsFragment.PERMISSIONS:
searchView.setVisibility(View.VISIBLE);
if (fragments[pageNo] != null) {
searchView.setOnQueryTextListener(fragments[pageNo]);
fragments[pageNo].resetFilter();
}
break;
case AppDetailsFragment.FEATURES:
case AppDetailsFragment.CONFIGURATION:
case AppDetailsFragment.SIGNATURES:
case AppDetailsFragment.SHARED_LIBRARY_FILES:
case AppDetailsFragment.NONE:
default:
searchView.setVisibility(View.GONE);
}
}
});
}
TabLayout tabLayout = findViewById(R.id.tab_layout);
new TabLayoutMediator(tabLayout, viewPager2, true,
(tab, position) -> tab.setText(mTabTitleIds.getText(position))).attach();
tabLayout.setupWithViewPager(viewPager);
}
@SuppressLint("RestrictedApi")
......@@ -182,21 +112,27 @@ public class AppDetailsActivity extends AppCompatActivity {
}
// For tab layout
private class AppDetailsFragmentStateAdapter extends FragmentStateAdapter {
AppDetailsFragmentStateAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
private class AppDetailsFragmentPagerAdapter extends FragmentPagerAdapter {
AppDetailsFragmentPagerAdapter(@NonNull FragmentManager fragmentManager) {
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
@NonNull
@Override
public Fragment createFragment(@AppDetailsFragment.Property int position) {
if (position == pageNo) fragmentLoaded = false;
return (fragments[position] = new AppDetailsFragment(position));
public AppDetailsFragment getItem(int position) {
if (fragments[position] == null) fragments[position] = new AppDetailsFragment(position);
return fragments[position];
}
@Override
public int getItemCount() {
public int getCount() {
return mTabTitleIds.length();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return mTabTitleIds.getText(position);
}
}
}
......@@ -57,20 +57,21 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.core.app.ShareCompat;
import androidx.core.content.FileProvider;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.fragments.RulesTypeSelectionDialogFragment;
import io.github.muntashirakon.AppManager.runner.Runner;
import io.github.muntashirakon.AppManager.types.ScrollSafeSwipeRefreshLayout;
import io.github.muntashirakon.AppManager.usage.AppUsageStatsManager;
import io.github.muntashirakon.AppManager.utils.AppPref;
import io.github.muntashirakon.AppManager.utils.ListItemCreator;
import io.github.muntashirakon.AppManager.utils.RunnerUtils;
import io.github.muntashirakon.AppManager.utils.Tuple;
import io.github.muntashirakon.AppManager.utils.Utils;
import static io.github.muntashirakon.AppManager.utils.IOUtils.deleteDir;
public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
public class AppInfoActivity extends AppCompatActivity implements ScrollSafeSwipeRefreshLayout.OnRefreshListener {
public static final String EXTRA_PACKAGE_NAME = "pkg";
private static final String UID_STATS_PATH = "/proc/uid_stat/";
......@@ -100,7 +101,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
@SuppressLint("SimpleDateFormat")
private SimpleDateFormat mDateFormatter = new SimpleDateFormat("EE LLL dd yyyy kk:mm:ss");
private ListItemCreator mList;
private SwipeRefreshLayout mSwipeRefresh;
private ScrollSafeSwipeRefreshLayout mSwipeRefresh;
private int mAccentColor;
private CharSequence mPackageLabel;
private ProgressIndicator mProgressIndicator;
......@@ -278,12 +279,18 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
if ((mApplicationInfo.flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0)
addChip(R.string.requested_large_heap, R.color.red);
if ((mApplicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0)
addChip(R.string.stopped, R.color.blue_green);
if (!mApplicationInfo.enabled) addChip(R.string.disabled_app, R.color.disabled_app);
addChip(R.string.stopped, R.color.stopped);
if (!mApplicationInfo.enabled) addChip(R.string.disabled_app, R.color.disabled_user);
}
private void setHorizontalView() {
mHorizontalLayout.removeAllViews();
// Set open
final Intent launchIntentForPackage = mPackageManager.getLaunchIntentForPackage(mPackageName);
if (launchIntentForPackage != null) {
addToHorizontalLayout(R.string.launch_app, R.drawable.ic_open_in_new_black_24dp)
.setOnClickListener(v -> startActivity(launchIntentForPackage));
}
// Set uninstall
addToHorizontalLayout(R.string.uninstall, R.drawable.ic_delete_black_24dp).setOnClickListener(v -> {
final boolean isSystemApp = (mApplicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
......@@ -294,7 +301,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
R.string.uninstall_system_app_message : R.string.uninstall_app_message)
.setPositiveButton(R.string.uninstall, (dialog, which) -> new Thread(() -> {
// Try without root first then with root
if (Runner.run(this, String.format("pm uninstall --user 0 %s", mPackageName)).isSuccessful()) {
if (RunnerUtils.uninstallPackage(mPackageName).isSuccessful()) {
runOnUiThread(() -> {
Toast.makeText(mActivity, String.format(getString(R.string.uninstalled_successfully), mPackageLabel), Toast.LENGTH_LONG).show();
finish();
......@@ -318,7 +325,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
if (mApplicationInfo.enabled) {
// Disable app
addToHorizontalLayout(R.string.disable, R.drawable.ic_block_black_24dp).setOnClickListener(v -> new Thread(() -> {
if (Runner.run(this, String.format("pm disable %s", mPackageName)).isSuccessful()) {
if (RunnerUtils.disablePackage(mPackageName).isSuccessful()) {
// Refresh
runOnUiThread(this::getPackageInfoOrFinish);
} else {
......@@ -328,7 +335,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
} else {
// Enable app
addToHorizontalLayout(R.string.enable, R.drawable.ic_baseline_get_app_24).setOnClickListener(v -> new Thread(() -> {
if (Runner.run(this, String.format("pm enable %s", mPackageName)).isSuccessful()) {
if (RunnerUtils.enablePackage(mPackageName).isSuccessful()) {
// Refresh
runOnUiThread(this::getPackageInfoOrFinish);
} else {
......@@ -339,7 +346,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
// Force stop
if ((mApplicationInfo.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
addToHorizontalLayout(R.string.force_stop, R.drawable.ic_baseline_power_settings_new_24).setOnClickListener(v -> new Thread(() -> {
if (Runner.run(this, String.format("am force-stop %s", mPackageName)).isSuccessful()) {
if (RunnerUtils.forceStopPackage(mPackageName).isSuccessful()) {
// Refresh
runOnUiThread(this::getPackageInfoOrFinish);
} else {
......@@ -591,13 +598,13 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
private List<String> getSharedPrefs(@NonNull String sourceDir) {
File sharedPath = new File(sourceDir + "/shared_prefs");
return Runner.run(this, String.format("ls %s/*.xml", sharedPath.getAbsolutePath())).getOutputAsList();
return Runner.runCommand(String.format("ls %s/*.xml", sharedPath.getAbsolutePath())).getOutputAsList();
}
private List<String> getDatabases(@NonNull String sourceDir) {
File sharedPath = new File(sourceDir + "/databases");
// FIXME: SQLite db doesn't necessarily have .db extension
return Runner.run(this, String.format("ls %s/*.db", sharedPath.getAbsolutePath())).getOutputAsList();
return Runner.runCommand(String.format("ls %s/*.db", sharedPath.getAbsolutePath())).getOutputAsList();
}
private void openAsFolderInFM(String dir) {
......@@ -665,7 +672,9 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
.setMessage(R.string.grant_usage_acess_message)
.setPositiveButton(R.string.go, (dialog, which) -> startActivityForResult(new Intent(
Settings.ACTION_USAGE_ACCESS_SETTINGS), 0))
.setNegativeButton(getString(android.R.string.cancel), null)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.never_ask, (dialog, which) ->
AppPref.getInstance().setPref(AppPref.PREF_USAGE_ACCESS_ENABLED, false))
.setCancelable(false)
.show();
return;
......@@ -694,7 +703,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
if (AppPref.isRootEnabled() || AppPref.isAdbEnabled()) {
mList.setOpen(v -> new Thread(() -> {
// Clear data
if (Runner.run(this, String.format("pm clear %s", mPackageName)).isSuccessful()) {
if (RunnerUtils.clearPackageData(mPackageName).isSuccessful()) {
runOnUiThread(this::getPackageInfoOrFinish);
}
}).start());
......@@ -718,7 +727,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
String extCache = cacheDir.getAbsolutePath().replace(getPackageName(), mPackageName);
command.append(" ").append(extCache);
}
if (Runner.run(this, command.toString()).isSuccessful()) {
if (Runner.runCommand(command.toString()).isSuccessful()) {
runOnUiThread(this::getPackageInfoOrFinish);
}
}).start());
......
......@@ -43,9 +43,9 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
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.types.ScrollSafeSwipeRefreshLayout;
import io.github.muntashirakon.AppManager.usage.AppUsageStatsManager;
import io.github.muntashirakon.AppManager.usage.Utils.IntervalType;
import io.github.muntashirakon.AppManager.utils.Tuple;
......@@ -56,14 +56,15 @@ 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, SwipeRefreshLayout.OnRefreshListener {
public class AppUsageActivity extends AppCompatActivity implements ListView.OnItemClickListener, ScrollSafeSwipeRefreshLayout.OnRefreshListener {
@IntDef(value = {
SORT_BY_APP_LABEL,
SORT_BY_LAST_USED,
SORT_BY_MOBILE_DATA,
SORT_BY_PACKAGE_NAME,
SORT_BY_SCREEN_TIME,
SORT_BY_TIMES_OPENED
SORT_BY_TIMES_OPENED,
SORT_BY_WIFI_DATA
})
private @interface SortOrder {}
private static final int SORT_BY_APP_LABEL = 0;
......@@ -72,17 +73,20 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
private static final int SORT_BY_PACKAGE_NAME = 3;
private static final int SORT_BY_SCREEN_TIME = 4;
private static final int SORT_BY_TIMES_OPENED = 5;
private static final int SORT_BY_WIFI_DATA = 6;
private static final int[] sSortMenuItemIdsMap = {
R.id.action_sort_by_app_label, R.id.action_sort_by_last_used,
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};
R.id.action_sort_by_screen_time, R.id.action_sort_by_times_opened,
R.id.action_sort_by_wifi_data};
private ProgressIndicator mProgressIndicator;
private SwipeRefreshLayout mSwipeRefresh;
private ScrollSafeSwipeRefreshLayout mSwipeRefresh;
private AppUsageAdapter mAppUsageAdapter;
List<AppUsageStatsManager.PackageUS> mPackageUSList;
private static long totalScreenTime;
private static PackageManager mPackageManager;
private @IntervalType int current_interval = USAGE_TODAY;
private @SortOrder int mSortBy;
......@@ -107,6 +111,8 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
listView.setAdapter(mAppUsageAdapter);
listView.setOnItemClickListener(this);
mPackageManager = getPackageManager();
mSwipeRefresh = findViewById(R.id.swipe_refresh);
mSwipeRefresh.setColorSchemeColors(Utils.getThemeColor(this, android.R.attr.colorAccent));
mSwipeRefresh.setProgressBackgroundColorSchemeColor(Utils.getThemeColor(this, android.R.attr.colorPrimary));
......@@ -143,6 +149,12 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
else getAppUsage();
}
@Override
protected void onDestroy() {
mPackageManager = null;
super.onDestroy();
}
@Override
public void onRefresh() {
mSwipeRefresh.setRefreshing(false);
......@@ -197,6 +209,10 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
setSortBy(SORT_BY_TIMES_OPENED);
item.setChecked(true);
return true;
case R.id.action_sort_by_wifi_data:
setSortBy(SORT_BY_WIFI_DATA);
item.setChecked(true);
return true;
}
return super.onOptionsItemSelected(item);
}
......@@ -229,15 +245,19 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
case SORT_BY_LAST_USED:
return -o1.lastUsageTime.compareTo(o2.lastUsageTime);
case SORT_BY_MOBILE_DATA:
Long o1Data = o1.mobileData.getFirst() + o1.mobileData.getSecond();
Long o2Data = o2.mobileData.getFirst() + o2.mobileData.getSecond();
return -o1Data.compareTo(o2Data);
Long o1MData = o1.mobileData.getFirst() + o1.mobileData.getSecond();
Long o2MData = o2.mobileData.getFirst() + o2.mobileData.getSecond();
return -o1MData.compareTo(o2MData);
case SORT_BY_PACKAGE_NAME:
return o1.packageName.compareTo(o2.packageName);
return o1.packageName.compareToIgnoreCase(o2.packageName);
case SORT_BY_SCREEN_TIME:
return -o1.screenTime.compareTo(o2.screenTime);
case SORT_BY_TIMES_OPENED:
return -o1.timesOpened.compareTo(o2.timesOpened);
case SORT_BY_WIFI_DATA:
Long o1WData = o1.wifiData.getFirst() + o1.wifiData.getSecond();
Long o2WData = o2.wifiData.getFirst() + o2.wifiData.getSecond();
return -o1WData.compareTo(o2WData);
}
return 0;
}));
......@@ -297,7 +317,6 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
private LayoutInflater mLayoutInflater;
private List<AppUsageStatsManager.PackageUS> mAdapterList;
private static PackageManager mPackageManager;
private Activity mActivity;
static class ViewHolder {
......@@ -315,7 +334,6 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
AppUsageAdapter(@NonNull Activity activity) {
mLayoutInflater = activity.getLayoutInflater();
mPackageManager = activity.getPackageManager();
mActivity = activity;
}
......
......@@ -43,7 +43,7 @@ import androidx.core.content.ContextCompat;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.appops.AppOpsManager;
import io.github.muntashirakon.AppManager.appops.AppOpsService;
import io.github.muntashirakon.AppManager.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.runner.Runner;
import io.github.muntashirakon.AppManager.utils.AppPref;
import io.github.muntashirakon.AppManager.utils.Utils;
......@@ -140,6 +140,12 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
refresh();
}
@Override
protected void onDestroy() {
mPackageManager = null;
super.onDestroy();
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
......@@ -290,7 +296,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
if (applicationInfo != null) {
holder.forceStopBtn.setVisibility(View.VISIBLE);
holder.forceStopBtn.setOnClickListener(v -> new Thread(() -> {
if (Runner.run(mActivity, String.format("am force-stop %s", applicationInfo.packageName)).isSuccessful()) {
if (Runner.runCommand(String.format("am force-stop %s", applicationInfo.packageName)).isSuccessful()) {
mActivity.runOnUiThread(() -> mActivity.refresh());
} else {
mActivity.runOnUiThread(() -> Toast.makeText(mActivity, String.format(mActivity.getString(R.string.failed_to_stop), processName), Toast.LENGTH_LONG).show());
......@@ -327,7 +333,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
if ((processItem.pid >= 10000 || enableKillForSystem) && !isAdbMode) {
holder.killBtn.setVisibility(View.VISIBLE);
holder.killBtn.setOnClickListener(v -> new Thread(() -> {
if (Runner.run(mActivity, String.format(Locale.ROOT, "kill -9 %d", processItem.pid)).isSuccessful()) {
if (Runner.runCommand(String.format(Locale.ROOT, "kill -9 %d", processItem.pid)).isSuccessful()) {
mActivity.runOnUiThread(() -> mActivity.refresh());
} else {
mActivity.runOnUiThread(() -> Toast.makeText(mActivity, String.format(mActivity.getString(R.string.failed_to_stop), processName), Toast.LENGTH_LONG).show());
......@@ -425,7 +431,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
@Override
public void run() {
List<ApplicationInfo> applicationInfoList = mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);
Runner.run(RunningAppsActivity.this, "ps -dwZ -o PID,PPID,RSS,VSZ,USER,UID,STAT,NAME | grep -v :kernel:");
Runner.runCommand("ps -dwZ -o PID,PPID,RSS,VSZ,USER,UID,STAT,NAME | grep -v :kernel:");
if (Runner.getLastResult().isSuccessful()) {
List<String> processInfoLines = Runner.getLastResult().getOutputAsList(1);
HashMap<String, ProcessItem> processList = new HashMap<>();
......
......@@ -19,7 +19,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.fragments.ImportExportDialogFragment;
import io.github.muntashirakon.AppManager.types.FullscreenDialog;
import io.github.muntashirakon.AppManager.utils.AppPref;
......
......@@ -173,7 +173,7 @@ public class SharedPrefsActivity extends AppCompatActivity implements
return true;
case R.id.action_delete:
// Make sure it's a file and then delete
boolean isSuccess = Runner.run(this, String.format("[ -f '%s' ] && rm -f '%s'",
boolean isSuccess = Runner.runCommand(String.format("[ -f '%s' ] && rm -f '%s'",
mSharedPrefFile, mSharedPrefFile)).isSuccessful();
if (isSuccess) {
Toast.makeText(this, R.string.deleted_successfully, Toast.LENGTH_LONG).show();
......@@ -282,7 +282,7 @@ public class SharedPrefsActivity extends AppCompatActivity implements
@Override
public void run() {
String sharedPrefPath = mTempSharedPrefFile.getAbsolutePath();
if(!Runner.run(SharedPrefsActivity.this, String.format("cp '%s' '%s' && chmod 0666 '%s'", mSharedPrefFile,
if(!Runner.runCommand(String.format("cp '%s' '%s' && chmod 0666 '%s'", mSharedPrefFile,
sharedPrefPath, sharedPrefPath)).isSuccessful()) {
runOnUiThread(SharedPrefsActivity.this::finish);
}
......@@ -337,7 +337,7 @@ public class SharedPrefsActivity extends AppCompatActivity implements
xmlSerializer.flush();
xmlFile.write(stringWriter.toString().getBytes());
xmlFile.close();
return Runner.run(this, String.format("cp '%s' '%s' && chmod 0666 '%s'", sharedPrefsFile,
return Runner.runCommand(String.format("cp '%s' '%s' && chmod 0666 '%s'", sharedPrefsFile,
mSharedPrefFile, mSharedPrefFile)).isSuccessful();
} catch (IOException e) {
e.printStackTrace();
......
......@@ -31,7 +31,6 @@ 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;
......@@ -57,27 +56,18 @@ class AppOpsService implements IAppOpsService {
public String checkOperation(int op, int uid, @Nullable String packageName)
throws Exception {
String opStr = AppOpsManager.opToName(op);
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) {
return parseOpName(output.get(0)).getMode();
} else if (output.size() == 2) {
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");
try {
// Check among all
AppOpsManager.PackageOps packageOps = getOpsForPackage(uid, packageName, null).get(0);
List<AppOpsManager.OpEntry> entries = packageOps.getOps();
// Iterate in backward direction to get only the last value of the duplicate app ops
for (int i = entries.size()-1; i>=0; --i) {
if (entries.get(i).getOp() == op) return entries.get(i).getMode();
}
}
packageOps = getOpsForPackage(uid, packageName, new int[]{op}).get(0);
entries = packageOps.getOps();
if (entries.size() == 1) return entries.get(0).getMode();
} catch (Exception ignore) {}
throw new Exception("Failed to check operation " + opStr);
}
......@@ -102,14 +92,19 @@ class AppOpsService implements IAppOpsService {
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
} else if (output.size() > 2) {
// In some cases, due to some bugs, output is more than two lines.
// If that's the case, add only the last line.
lines.add(output.get(output.size()-1));
}
// if (!isSuccessful) throw new Exception("Failed to get operations for package " + packageName);
}
}
List<AppOpsManager.OpEntry> opEntries = new ArrayList<>();
for(String line: lines) {
// Iterate in backward direction to get only the last value of the duplicate app ops
for(int i = lines.size()-1; i >= 0; --i) {
try {
opEntries.add(parseOpName(line));
opEntries.add(parseOpName(lines.get(i)));
} catch (Exception ignored) {}
}
AppOpsManager.PackageOps packageOps = new AppOpsManager.PackageOps(packageName, uid, opEntries);
......@@ -143,7 +138,7 @@ class AppOpsService implements IAppOpsService {
* @param command The command to run
*/
private void runCommand(String command) {
Runner.Result result = Runner.run(context, command);
Runner.Result result = Runner.runCommand(command);
isSuccessful = result.isSuccessful();
output = result.getOutputAsList();
}
......@@ -176,14 +171,18 @@ class AppOpsService implements IAppOpsService {
String modeStr = matcher.group(2);
if (opStr == null || modeStr == null)
throw new Exception("Op name or mode cannot be empty");
// Handle Unknown(op)
if (opStr.startsWith("Unknown("))
opStr = opStr.substring(UNKNOWN_OP_SKIP, opStr.length()-1);
// Handle Unknown(op), MIUIOP(op), etc.
if (opStr.endsWith(")")) {
try {
int leftBracket = opStr.indexOf('(');
opStr = opStr.substring(leftBracket + 1, opStr.length() - 1);
} catch (Exception ignore) {}
}
final String finalOpStr = opStr; // Save the op str before modifying it
if (opStr.startsWith("OP_"))
opStr = opStr.substring(OP_PREFIX_OP_SKIP);
// FIXME: Check old opStr as well
// Handle mode=5
// Handle mode=(mode)
if (modeStr.startsWith("mode="))
modeStr = modeStr.substring(UNKNOWN_MODE_SKIP);
int op = AppOpsManager.OP_NONE;
......
......@@ -9,13 +9,16 @@ 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.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.runner.Runner;
import io.github.muntashirakon.AppManager.storage.compontents.ExternalComponentsImporter;
import io.github.muntashirakon.AppManager.utils.RunnerUtils;
public class BatchOpsManager {
@IntDef(value = {
OP_BACKUP_APK,
OP_BACKUP_DATA,
OP_BLOCK_TRACKERS,
OP_CLEAR_DATA,
OP_DISABLE,
OP_DISABLE_BACKGROUND,
......@@ -26,18 +29,19 @@ public class BatchOpsManager {
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;
public static final int OP_BLOCK_TRACKERS = 2;
public static final int OP_CLEAR_DATA = 3;
public static final int OP_DISABLE = 4;
public static final int OP_DISABLE_BACKGROUND = 5;
public static final int OP_EXPORT_RULES = 6;
public static final int OP_KILL = 7;
public static final int OP_UNINSTALL = 8;
private Runner runner;
private Context context;
public BatchOpsManager(Context context) {
this.context = context;
this.runner = Runner.getInstance(context);
this.runner = Runner.getInstance();
}
private List<String> packageNames;
......@@ -51,6 +55,7 @@ public class BatchOpsManager {
case OP_BACKUP_APK: // TODO
case OP_BACKUP_DATA: // TODO
break;
case OP_BLOCK_TRACKERS: return opBlockTrackers();
case OP_CLEAR_DATA: return opClearData();
case OP_DISABLE: return opDisable();
case OP_DISABLE_BACKGROUND: return opDisableBackground();
......@@ -76,10 +81,25 @@ public class BatchOpsManager {
return lastResult;
}
private Result opBlockTrackers() {
final List<String> failedPkgList = ExternalComponentsImporter.applyFromTrackingComponents(context, packageNames);
return lastResult = new Result() {
@Override
public boolean isSuccessful() {
return failedPkgList.size() == 0;
}
@Override
public List<String> failedPackages() {
return failedPkgList;
}
};
}
@NonNull
private Result opClearData() {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm clear %s", packageName));
addCommand(packageName, String.format(Locale.ROOT, RunnerUtils.CMD_CLEAR_PACKAGE_DATA, packageName));
}
return runOpAndFetchResults();
}
......@@ -87,7 +107,7 @@ public class BatchOpsManager {
@NonNull
private Result opDisable() {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm disable %s", packageName));
addCommand(packageName, String.format(Locale.ROOT, RunnerUtils.CMD_DISABLE_PACKAGE, packageName));
}
return runOpAndFetchResults();
}
......@@ -95,7 +115,7 @@ public class BatchOpsManager {
@NonNull
private Result opDisableBackground() {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "appops set %s 63 %d", packageName, AppOpsManager.MODE_IGNORED));
addCommand(packageName, String.format(Locale.ROOT, RunnerUtils.CMD_APP_OPS_SET_MODE_INT, packageName, 63, AppOpsManager.MODE_IGNORED));
}
Result result = runOpAndFetchResults();
List<String> failedPackages = result.failedPackages();
......@@ -112,14 +132,14 @@ public class BatchOpsManager {
@NonNull
private Result opKill() {
for (String packageName : packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pidof %s", packageName), false);
addCommand(packageName, String.format(Locale.ROOT, RunnerUtils.CMD_PID_PACKAGE, 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)));
addCommand(packageNames.get(i), String.format(Locale.ROOT, RunnerUtils.CMD_KILL_SIG9, pidOrPackageNames.get(i)));
}
}
return runOpAndFetchResults();
......@@ -128,7 +148,7 @@ public class BatchOpsManager {
@NonNull
private Result opUninstall() {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm uninstall --user 0 %s", packageName));
addCommand(packageName, String.format(Locale.ROOT, RunnerUtils.CMD_UNINSTALL_PACKAGE, packageName));
}
return runOpAndFetchResults();
}
......@@ -143,7 +163,7 @@ public class BatchOpsManager {
@NonNull
private Result runOpAndFetchResults() {
Runner.Result result = runner.run();
Runner.Result result = runner.runCommand();
lastResult = new Result() {
@Override
public boolean isSuccessful() {
......
......@@ -24,7 +24,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.compontents.ExternalComponentsImporter;
import io.github.muntashirakon.AppManager.storage.compontents.ExternalComponentsImporter;
import io.github.muntashirakon.AppManager.utils.Tuple;
public class ImportExportDialogFragment extends DialogFragment {
......
package io.github.muntashirakon.AppManager.runner;
import android.content.Context;
import android.text.TextUtils;
import java.io.IOException;
......@@ -10,12 +9,8 @@ import java.util.List;
import io.github.muntashirakon.AppManager.adb.AdbShell;
public class AdbShellRunner extends Runner {
protected AdbShellRunner(Context context) {
super(context);
}
@Override
public Result run() {
public Result runCommand() {
try {
AdbShell.CommandResult result = AdbShell.run(TextUtils.join("; ", commands));
clear();
......@@ -56,6 +51,6 @@ public class AdbShellRunner extends Runner {
protected Result run(String command) {
clear();
addCommand(command);
return run();
return runCommand();
}
}
package io.github.muntashirakon.AppManager.runner;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import com.jaredrummler.android.shell.CommandResult;
......@@ -10,23 +9,19 @@ import com.jaredrummler.android.shell.Shell;
import java.util.List;
public class RootShellRunner extends Runner {
protected RootShellRunner(Context context) {
super(context);
}
@SuppressLint("StaticFieldLeak")
private static RootShellRunner rootShellRunner;
public static RootShellRunner getInstance(Context context) {
if (rootShellRunner == null) rootShellRunner = new RootShellRunner(context.getApplicationContext());
public static RootShellRunner getInstance() {
if (rootShellRunner == null) rootShellRunner = new RootShellRunner();
return rootShellRunner;
}
public static Result run(Context context, String command) {
return getInstance(context).run(command);
public static Result runCommand(String command) {
return getInstance().run(command);
}
@Override
public Result run() {
public Result runCommand() {
CommandResult result = Shell.SU.run(TextUtils.join("; ", commands));
clear();
lastResult = new Result() {
......@@ -62,6 +57,6 @@ public class RootShellRunner extends Runner {
protected Result run(String command) {
clear();
addCommand(command);
return run();
return runCommand();
}
}
......@@ -6,6 +6,7 @@ import android.content.Context;
import java.util.ArrayList;
import java.util.List;
import io.github.muntashirakon.AppManager.AppManager;
import io.github.muntashirakon.AppManager.utils.AppPref;
public class Runner {
......@@ -21,21 +22,24 @@ public class Runner {
@SuppressLint("StaticFieldLeak")
private static Runner runner;
private static boolean isAdb = false;
public static Runner getInstance(Context context) {
public static Runner getInstance() {
if (runner == null || (isAdb && !AppPref.isAdbEnabled()) || (!isAdb && AppPref.isAdbEnabled())) {
if (AppPref.isRootEnabled()) {
runner = new RootShellRunner(context.getApplicationContext());
runner = new RootShellRunner();
isAdb = false;
} else {
runner = new AdbShellRunner(context.getApplicationContext());
} else if (AppPref.isAdbEnabled()) {
runner = new AdbShellRunner();
isAdb = true;
} else {
runner = new UserShellRunner();
isAdb = false;
}
}
return runner;
}
public static Result run(Context context, String command) {
return getInstance(context).run(command);
synchronized public static Result runCommand(String command) {
return getInstance().run(command);
}
protected List<String> commands;
......@@ -47,7 +51,7 @@ public class Runner {
commands.clear();
}
public Result run() {
synchronized public Result runCommand() {
return null;
}
......@@ -57,8 +61,8 @@ public class Runner {
}
protected Context context;
protected Runner(Context context) {
this.context = context;
protected Runner() {
this.context = AppManager.getContext();
this.commands = new ArrayList<>();
}
......
package io.github.muntashirakon.AppManager.runner;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import com.jaredrummler.android.shell.CommandResult;
import com.jaredrummler.android.shell.Shell;
import java.util.List;
public class UserShellRunner extends Runner {
@SuppressLint("StaticFieldLeak")
private static UserShellRunner rootShellRunner;
public static UserShellRunner getInstance() {
if (rootShellRunner == null) rootShellRunner = new UserShellRunner();
return rootShellRunner;
}
public static Result runCommand(String command) {
return getInstance().run(command);
}
@Override
public Result runCommand() {
CommandResult result = Shell.SH.run(TextUtils.join("; ", commands));
clear();
lastResult = new Result() {
@Override
public boolean isSuccessful() {
return result.isSuccessful();
}
@Override
public List<String> getOutputAsList() {
return result.stdout;
}
@Override
public List<String> getOutputAsList(int first_index) {
return result.stdout.subList(first_index, result.stdout.size());
}
@Override
public List<String> getOutputAsList(int first_index, int length) {
return result.stdout.subList(first_index, first_index + length);
}
@Override
public String getOutput() {
return result.getStdout();
}
};
return lastResult;
}
@Override
protected Result run(String command) {
clear();
addCommand(command);
return runCommand();
}
}
......@@ -14,7 +14,7 @@ import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.muntashirakon.AppManager.AppManager;
import io.github.muntashirakon.AppManager.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
/**
* Export rules to external directory either for a single package or multiple packages.
......
......@@ -17,7 +17,7 @@ import java.util.StringTokenizer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.muntashirakon.AppManager.AppManager;
import io.github.muntashirakon.AppManager.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
/**
* Rules importer is used to import internal rules to App Manager. Rules should only be imported
......
......@@ -11,20 +11,27 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringDef;
import io.github.muntashirakon.AppManager.appops.AppOpsManager;
import io.github.muntashirakon.AppManager.appops.AppOpsService;
import io.github.muntashirakon.AppManager.runner.Runner;
import io.github.muntashirakon.AppManager.utils.RunnerUtils;
public class RulesStorageManager implements Closeable {
@Override
public void close() {
if (!readOnly) commit();
}
@StringDef(value = {
COMPONENT_BLOCKED,
COMPONENT_TO_BE_BLOCKED,
COMPONENT_TO_BE_UNBLOCKED
})
public @interface ComponentStatus {}
public static final String COMPONENT_BLOCKED = "true"; // To preserve compatibility
public static final String COMPONENT_TO_BE_BLOCKED = "false"; // To preserve compatibility
public static final String COMPONENT_TO_BE_UNBLOCKED = "unblocked";
public enum Type {
ACTIVITY,
......@@ -69,6 +76,19 @@ public class RulesStorageManager implements Closeable {
loadEntries();
}
public void setReadOnly() {
this.readOnly = true;
}
public void setMutable() {
this.readOnly = false;
}
@Override
public void close() {
if (!readOnly) commit();
}
public Entry get(String name) {
for (Entry entry: entries) if (entry.name.equals(name)) return entry;
return null;
......@@ -111,12 +131,12 @@ public class RulesStorageManager implements Closeable {
entries.remove(removableEntry);
}
protected void setComponent(String name, Type componentType, Boolean isApplied) {
protected void setComponent(String name, Type componentType, @ComponentStatus String componentStatus) {
Entry entry = new Entry();
entry.name = name;
entry.type = componentType;
entry.extra = isApplied;
addEntry(entry); // FIXME
entry.extra = componentStatus;
addEntry(entry);
}
public void setAppOp(String name, @AppOpsManager.Mode int mode) {
......@@ -141,13 +161,13 @@ public class RulesStorageManager implements Closeable {
}
public void applyAppOpsAndPerms(boolean apply) {
AppOpsService appOpsService = new AppOpsService(context);
Runner runner = Runner.getInstance();
if (apply) {
// Apply all app ops
List<Entry> appOps = getAll(Type.APP_OP);
for (Entry appOp: appOps) {
try {
appOpsService.setMode(Integer.parseInt(appOp.name), -1, packageName, (Integer) appOp.extra);
runner.addCommand(String.format(Locale.ROOT, RunnerUtils.CMD_APP_OPS_SET, packageName, Integer.parseInt(appOp.name), appOp.extra));
} catch (Exception e) {
e.printStackTrace();
}
......@@ -155,21 +175,28 @@ public class RulesStorageManager implements Closeable {
// Apply all permissions
List<Entry> permissions = getAll(Type.PERMISSION);
for (Entry permission: permissions) {
Runner.run(context, String.<