...
 
Commits (30)
......@@ -9,7 +9,7 @@ Yet another android package manager and viewer but...
- 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 block any activities, broadcast receivers, services or providers you like with native import/export as well as Watt and Blocker import support (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)
......
......@@ -9,9 +9,7 @@ android {
Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def name = versionProps['VERSION_NAME']
def code = versionProps['VERSION_CODE'].toInteger() + 1
versionProps['VERSION_CODE'] = code.toString()
versionProps.store(versionPropsFile.newWriter(), null)
def code = versionProps['VERSION_CODE'].toInteger()
defaultConfig {
applicationId 'io.github.muntashirakon.AppManager'
......@@ -50,7 +48,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.android.material:material:1.3.0-alpha01'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.jaredrummler:android-shell:1.0.0'
......
......@@ -35,7 +35,7 @@ public class MainLoader extends AsyncTaskLoader<List<ApplicationItem>> {
public List<ApplicationItem> loadInBackground() {
List<ApplicationItem> itemList = new ArrayList<>();
String pName;
final Boolean isRootEnabled = (Boolean) AppPref.get(getContext(), AppPref.PREF_ROOT_MODE_ENABLED, AppPref.TYPE_BOOLEAN);
final boolean isRootEnabled = AppPref.isRootEnabled();
if (MainActivity.packageList != null) {
String[] aList = MainActivity.packageList.split("[\\r\\n]+");
for (String s : aList) {
......
......@@ -33,19 +33,21 @@ import io.github.muntashirakon.AppManager.utils.Utils;
public class AppDetailsActivity extends AppCompatActivity {
public static final String EXTRA_PACKAGE_NAME = "pkg";
public static String mConstraint;
public static String sConstraint;
private String mPackageName;
private TypedArray mTabTitleIds;
AppDetailsFragmentStateAdapter appDetailsFragmentStateAdapter;
ViewPager2 viewPager2;
AppDetailsFragment[] fragments;
private int pageNo;
private boolean fragmentLoaded = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_details);
setSupportActionBar(findViewById(R.id.toolbar));
mConstraint = null;
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();
......@@ -82,10 +84,49 @@ public class AppDetailsActivity extends AppCompatActivity {
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);
switch (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:
......@@ -94,9 +135,9 @@ public class AppDetailsActivity extends AppCompatActivity {
case AppDetailsFragment.USES_PERMISSIONS:
case AppDetailsFragment.PERMISSIONS:
searchView.setVisibility(View.VISIBLE);
if (fragments[position] != null) {
searchView.setOnQueryTextListener(fragments[position]);
fragments[position].resetFilter();
if (fragments[pageNo] != null) {
searchView.setOnQueryTextListener(fragments[pageNo]);
fragments[pageNo].resetFilter();
}
break;
case AppDetailsFragment.FEATURES:
......@@ -149,6 +190,7 @@ public class AppDetailsActivity extends AppCompatActivity {
@NonNull
@Override
public Fragment createFragment(@AppDetailsFragment.Property int position) {
if (position == pageNo) fragmentLoaded = false;
return (fragments[position] = new AppDetailsFragment(position));
}
......
package io.github.muntashirakon.AppManager.activities;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.ComponentName;
......@@ -30,6 +31,7 @@ import android.widget.Toast;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.ProgressIndicator;
import com.google.classysharkandroid.utils.IOUtils;
......@@ -41,6 +43,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Objects;
......@@ -48,8 +51,8 @@ import java.util.Objects;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.core.app.ShareCompat;
......@@ -57,6 +60,7 @@ 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.usage.AppUsageStatsManager;
import io.github.muntashirakon.AppManager.utils.AppPref;
......@@ -80,6 +84,10 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
private static final String ACTIVITY_NAME_AURORA_DROID = "com.aurora.adroid.ui.activity.DetailsActivity";
private static final String ACTIVITY_NAME_AURORA_STORE = "com.aurora.store.ui.details.DetailsActivity";
private static final String MIME_TSV = "text/tab-separated-values";
private static final int REQUEST_CODE_BATCH_EXPORT = 441;
private PackageManager mPackageManager;
private String mPackageName;
private PackageInfo mPackageInfo;
......@@ -165,10 +173,39 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
infoIntent.setData(Uri.parse("package:" + mPackageName));
startActivity(infoIntent);
return true;
case R.id.action_export_blocking_rules:
@SuppressLint("SimpleDateFormat")
String fileName = "app_manager_rules_export-" + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime())) + ".am.tsv";
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(MIME_TSV);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, REQUEST_CODE_BATCH_EXPORT);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_BATCH_EXPORT) {
if (data != null) {
RulesTypeSelectionDialogFragment dialogFragment = new RulesTypeSelectionDialogFragment();
Bundle args = new Bundle();
ArrayList<String> packages = new ArrayList<>();
packages.add(mPackageName);
args.putInt(RulesTypeSelectionDialogFragment.ARG_MODE, RulesTypeSelectionDialogFragment.MODE_EXPORT);
args.putParcelable(RulesTypeSelectionDialogFragment.ARG_URI, data.getData());
args.putStringArrayList(RulesTypeSelectionDialogFragment.ARG_PKG, packages);
dialogFragment.setArguments(args);
dialogFragment.show(getSupportFragmentManager(), RulesTypeSelectionDialogFragment.TAG);
}
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
......@@ -221,7 +258,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
// Set App Version
TextView versionView = findViewById(R.id.version);
versionView.setText(String.format(getString(R.string.version), mPackageInfo.versionName,
versionView.setText(String.format(getString(R.string.version_name_with_code), mPackageInfo.versionName,
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ?
mPackageInfo.getLongVersionCode() : mPackageInfo.versionCode)));
......@@ -251,7 +288,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
addToHorizontalLayout(R.string.uninstall, R.drawable.ic_delete_black_24dp).setOnClickListener(v -> {
final boolean isSystemApp = (mApplicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
if (AppPref.isRootEnabled()) {
new AlertDialog.Builder(this, R.style.CustomDialog)
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(mPackageLabel)
.setMessage(isSystemApp ?
R.string.uninstall_system_app_message : R.string.uninstall_app_message)
......@@ -344,7 +381,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
sharedPrefs2[i] = new File(sharedPrefs.get(i)).getName();
}
addToHorizontalLayout(R.string.shared_prefs, R.drawable.ic_view_list_black_24dp)
.setOnClickListener(v -> new AlertDialog.Builder(this, R.style.CustomDialog)
.setOnClickListener(v -> new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(R.string.shared_prefs)
.setItems(sharedPrefs2, (dialog, which) -> {
Intent intent = new Intent(this, SharedPrefsActivity.class);
......@@ -367,7 +404,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
databases2[i] = databases.get(i);
}
addToHorizontalLayout(R.string.databases, R.drawable.ic_assignment_black_24dp)
.setOnClickListener(v -> new AlertDialog.Builder(this, R.style.CustomDialog)
.setOnClickListener(v -> new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(R.string.databases)
.setItems(databases2, null) // TODO
.setNegativeButton(android.R.string.ok, null)
......@@ -526,7 +563,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
// Net statistics
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if ((Boolean) AppPref.get(this, AppPref.PREF_USAGE_ACCESS_ENABLED, AppPref.TYPE_BOOLEAN)) {
if ((Boolean) AppPref.get(AppPref.PREF_USAGE_ACCESS_ENABLED, AppPref.TYPE_BOOLEAN)) {
Tuple<Tuple<Long, Long>, Tuple<Long, Long>> dataUsage = AppUsageStatsManager
.getWifiMobileUsageForPackage(this, mPackageName,
io.github.muntashirakon.AppManager.usage.Utils.USAGE_LAST_BOOT);
......@@ -548,7 +585,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
}
// Storage and Cache
if ((Boolean) AppPref.get(this, AppPref.PREF_USAGE_ACCESS_ENABLED, AppPref.TYPE_BOOLEAN))
if ((Boolean) AppPref.get(AppPref.PREF_USAGE_ACCESS_ENABLED, AppPref.TYPE_BOOLEAN))
getPackageSizeInfo();
}
......@@ -623,7 +660,7 @@ public class AppInfoActivity extends AppCompatActivity implements SwipeRefreshLa
}
} else {
if (!Utils.checkUsageStatsPermission(this)) {
new AlertDialog.Builder(this, R.style.CustomDialog)
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(R.string.grant_usage_access)
.setMessage(R.string.grant_usage_acess_message)
.setPositiveButton(R.string.go, (dialog, which) -> startActivityForResult(new Intent(
......
......@@ -2,7 +2,6 @@ package io.github.muntashirakon.AppManager.activities;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
......@@ -26,6 +25,7 @@ import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.ProgressIndicator;
import com.google.android.material.textview.MaterialTextView;
......@@ -41,7 +41,6 @@ import java.util.Locale;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
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;
......@@ -264,7 +263,7 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
}
private void promptForUsageStatsPermission() {
new AlertDialog.Builder(this, R.style.CustomDialog)
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(R.string.grant_usage_access)
.setMessage(R.string.grant_usage_acess_message)
.setPositiveButton(R.string.go, (dialog, which) -> startActivity(new Intent(
......@@ -277,7 +276,7 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
private void setUsageSummary() {
TextView timeUsed = findViewById(R.id.time_used);
TextView timeRange = findViewById(R.id.time_range);
timeUsed.setText(formattedTime(this, totalScreenTime));
timeUsed.setText(Utils.getFormattedDuration(this, totalScreenTime));
switch (current_interval) {
case USAGE_TODAY:
timeRange.setText(R.string.usage_today);
......@@ -293,36 +292,6 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
}
}
public static String formattedTime(Context context, long time) {
time /= 60000; // minutes
long month, day, hour, min;
month = time / 43200; time %= 43200;
day = time / 1440; time %= 1440;
hour = time / 60;
min = time % 60;
String fTime = "";
int count = 0;
if (month != 0){
fTime += String.format(context.getString(month > 0 ? R.string.usage_months : R.string.usage_month), month);
++count;
}
if (day != 0) {
fTime += (count > 0 ? " " : "") + String.format(context.getString(
day > 1 ? R.string.usage_days : R.string.usage_day), day);
++count;
}
if (hour != 0) {
fTime += (count > 0 ? " " : "") + String.format(context.getString(R.string.usage_hour), hour);
++count;
}
if (min != 0) {
fTime += (count > 0 ? " " : "") + String.format(context.getString(R.string.usage_min), min);
} else {
if (count == 0) fTime = context.getString(R.string.usage_less_than_a_minute);
}
return fTime;
}
static class AppUsageAdapter extends BaseAdapter {
static DateFormat sSimpleDateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss", Locale.getDefault());
......@@ -417,7 +386,7 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
mActivity.getString(R.string.one_time_opened)
: mActivity.getString(R.string.no_of_times_opened), packageUS.timesOpened);
// Set screen time
screenTimesWithTimesOpened += ", " + formattedTime(mActivity, packageUS.screenTime);
screenTimesWithTimesOpened += ", " + Utils.getFormattedDuration(mActivity, packageUS.screenTime);
holder.screenTime.setText(screenTimesWithTimesOpened);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Set data usage
......
......@@ -25,6 +25,7 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.ProgressIndicator;
import com.google.classysharkandroid.dex.DexLoaderBuilder;
import com.google.classysharkandroid.reflector.ClassesNamesList;
......@@ -43,7 +44,6 @@ import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.widget.SearchView;
......@@ -269,7 +269,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
if (Names[i - 1].equals(Names[i])) continue;
statsMsg.append(Names[i]).append("\n"); j++;
}
new AlertDialog.Builder(this, R.style.CustomDialog)
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(String.format(getString(R.string.trackers_and_classes), j, Names.length))
.setNegativeButton(android.R.string.ok, null)
.setMessage(statsMsg.toString()).show();
......@@ -308,7 +308,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
.replaceAll(" ", "&nbsp;").replaceAll("\n", "<br/>"), HtmlCompat.FROM_HTML_MODE_LEGACY));
showText.setMovementMethod(new ScrollingMovementMethod());
showText.setTextIsSelectable(true);
new AlertDialog.Builder(this, R.style.CustomDialog)
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(String.format(getString(R.string.trackers_and_classes),
totalTrackersFound, classList.size()))
.setView(showText)
......@@ -470,7 +470,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
mLayoutInflater = activity.getLayoutInflater();
mColorTransparent = Color.TRANSPARENT;
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.SEMI_TRANSPARENT);
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.semi_transparent);
mColorRed = ContextCompat.getColor(activity, R.color.red);
}
......
......@@ -69,8 +69,8 @@ public class ClassViewerActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
if (getIntent().getBooleanExtra(EXTRA_IS_WRAPPED, false))
setContentView(R.layout.activity_any_viewer_wrapped);
else
setContentView(R.layout.activity_any_viewer);
else setContentView(R.layout.activity_any_viewer);
setSupportActionBar(findViewById(R.id.toolbar));
mProgressIndicator = findViewById(R.id.progress_linear);
......
......@@ -105,7 +105,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
mAdapter = new RunningAppsAdapter(this);
mListView.setAdapter(mAdapter);
mConstraint = null;
enableKillForSystem = (boolean) AppPref.get(this, AppPref.PREF_ENABLE_KILL_FOR_SYSTEM, AppPref.TYPE_BOOLEAN);
enableKillForSystem = (boolean) AppPref.get(AppPref.PREF_ENABLE_KILL_FOR_SYSTEM, AppPref.TYPE_BOOLEAN);
refresh();
}
......@@ -127,7 +127,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
return true;
case R.id.action_toggle_kill:
enableKillForSystem = !enableKillForSystem;
AppPref.getInstance(this).setPref(AppPref.PREF_ENABLE_KILL_FOR_SYSTEM, enableKillForSystem);
AppPref.getInstance().setPref(AppPref.PREF_ENABLE_KILL_FOR_SYSTEM, enableKillForSystem);
refresh();
return true;
}
......@@ -199,7 +199,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
mLayoutInflater = activity.getLayoutInflater();
mColorTransparent = Color.TRANSPARENT;
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.SEMI_TRANSPARENT);
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.semi_transparent);
mColorRed = ContextCompat.getColor(activity, R.color.red);
}
......
package io.github.muntashirakon.AppManager.activities;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.switchmaterial.SwitchMaterial;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
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.fragments.ImportExportDialogFragment;
import io.github.muntashirakon.AppManager.types.FullscreenDialog;
import io.github.muntashirakon.AppManager.utils.AppPref;
public class SettingsActivity extends AppCompatActivity {
private static List<Integer> themeConst = Arrays.asList(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY, AppCompatDelegate.MODE_NIGHT_NO, AppCompatDelegate.MODE_NIGHT_YES);
private AppPref appPref;
private int currentTheme;
@Override
protected void onCreate(Bundle savedInstanceState) {
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);
appPref = AppPref.getInstance();
final SwitchMaterial rootSwitcher = findViewById(R.id.root_toggle_btn);
final SwitchMaterial blockingSwitcher = findViewById(R.id.blocking_toggle_btn);
final SwitchMaterial usageSwitcher = findViewById(R.id.usage_toggle_btn);
final MaterialCardView blockingView = findViewById(R.id.blocking_view);
final View blockingView = findViewById(R.id.blocking_view);
final TextView appThemeMsg = findViewById(R.id.app_theme_msg);
// Read pref
Boolean rootEnabled = (Boolean) appPref.getPref(AppPref.PREF_ROOT_MODE_ENABLED, AppPref.TYPE_BOOLEAN);
Boolean blockingEnabled = (Boolean) appPref.getPref(AppPref.PREF_GLOBAL_BLOCKING_ENABLED, AppPref.TYPE_BOOLEAN);
Boolean usageEnabled = (Boolean) appPref.getPref(AppPref.PREF_USAGE_ACCESS_ENABLED, AppPref.TYPE_BOOLEAN);
currentTheme = (int) appPref.getPref(AppPref.PREF_APP_THEME, AppPref.TYPE_INTEGER);
// Set changed values
rootSwitcher.setChecked(rootEnabled);
blockingView.setVisibility(rootEnabled ? View.VISIBLE : View.GONE);
blockingSwitcher.setChecked(blockingEnabled);
usageSwitcher.setChecked(usageEnabled);
final String[] themes = getResources().getStringArray(R.array.themes);
appThemeMsg.setText(String.format(Locale.getDefault(), getString(R.string.current_theme), themes[themeConst.indexOf(currentTheme)]));
// Set listeners
findViewById(R.id.app_theme).setOnClickListener(v ->
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(R.string.select_theme)
.setSingleChoiceItems(themes, themeConst.indexOf(currentTheme),
(dialog, which) -> currentTheme = themeConst.get(which))
.setPositiveButton(R.string.apply, (dialog, which) -> {
appPref.setPref(AppPref.PREF_APP_THEME, currentTheme);
AppCompatDelegate.setDefaultNightMode(currentTheme);
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
})
.setNegativeButton(android.R.string.cancel, null)
.create()
.show());
rootSwitcher.setOnCheckedChangeListener((buttonView, isChecked) -> {
appPref.setPref(AppPref.PREF_ROOT_MODE_ENABLED, isChecked);
blockingView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
});
blockingSwitcher.setOnCheckedChangeListener((buttonView, isChecked) -> {
appPref.setPref(AppPref.PREF_GLOBAL_BLOCKING_ENABLED, isChecked);
Boolean rootEnabled1 = (Boolean) appPref.getPref(AppPref.PREF_ROOT_MODE_ENABLED, AppPref.TYPE_BOOLEAN);
if (rootEnabled1 && isChecked) {
if (AppPref.isRootEnabled() && isChecked) {
ComponentsBlocker.applyAllRules(this);
}
});
......@@ -64,6 +96,26 @@ public class SettingsActivity extends AppCompatActivity {
} else {
findViewById(R.id.import_view).setVisibility(View.GONE);
}
findViewById(R.id.about_view).setOnClickListener(v -> {
View view = getLayoutInflater().inflate(R.layout.dialog_about, null);
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String version = packageInfo.versionName;
long versionCode;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
versionCode = packageInfo.getLongVersionCode();
} else versionCode = packageInfo.versionCode;
((TextView) view.findViewById(R.id.version)).setText(String.format(Locale.ROOT,
"%s (%d)", version, versionCode));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
new FullscreenDialog(this)
.setTitle(R.string.about)
.setView(view)
.show();
});
}
@Override
......
......@@ -366,7 +366,7 @@ public class SharedPrefsActivity extends AppCompatActivity implements
mLayoutInflater = activity.getLayoutInflater();
mColorTransparent = Color.TRANSPARENT;
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.SEMI_TRANSPARENT);
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.semi_transparent);
mColorRed = ContextCompat.getColor(activity, R.color.red);
}
......
......@@ -2,6 +2,7 @@ package io.github.muntashirakon.AppManager.appops;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.List;
......@@ -178,6 +179,7 @@ class AppOpsService implements IAppOpsService {
// Handle Unknown(op)
if (opStr.startsWith("Unknown("))
opStr = opStr.substring(UNKNOWN_OP_SKIP, opStr.length()-1);
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
......@@ -192,19 +194,22 @@ class AppOpsService implements IAppOpsService {
long accessTime = getTime(matcher, 3);
long rejectTime = getTime(matcher, 9);
long duration = getTime(matcher, 16);
return new AppOpsManager.OpEntry(op, opStr, running, modeStr, accessTime, rejectTime, duration, null, null);
return new AppOpsManager.OpEntry(op, finalOpStr, running, modeStr, accessTime, rejectTime, duration, null, null);
}
throw new Exception("Failed to parse line");
}
private static long getTime(@NonNull Matcher matcher, int start) {
long time = 1;
long time = 0;
String sign = matcher.group(start);
if (sign == null) return 0;
String tmp;
for(int i = 0; i<5; ++i) {
tmp = removeLastChar(matcher.group(start+i));
if (tmp != null && !tmp.equals("")) time += Integer.parseInt(tmp) * TIME[i];
tmp = removeLastChar(matcher.group(start+i+1));
if (!TextUtils.isEmpty(tmp)) {
//noinspection ConstantConditions
time += Long.parseLong(tmp) * TIME[i];
}
}
return sign.equals("-") ? -time : time;
}
......
......@@ -11,7 +11,6 @@ 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 = {
......@@ -55,8 +54,7 @@ public class BatchOpsManager {
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_EXPORT_RULES: break; // Done in the main activity
case OP_KILL: return opKill();
case OP_UNINSTALL: return opUninstall();
}
......@@ -80,7 +78,7 @@ public class BatchOpsManager {
@NonNull
private Result opClearData() {
for(String packageName: packageNames) {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm clear %s", packageName));
}
return runOpAndFetchResults();
......@@ -88,7 +86,7 @@ public class BatchOpsManager {
@NonNull
private Result opDisable() {
for(String packageName: packageNames) {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm disable %s", packageName));
}
return runOpAndFetchResults();
......@@ -96,7 +94,7 @@ public class BatchOpsManager {
@NonNull
private Result opDisableBackground() {
for(String packageName: packageNames) {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "appops set %s 63 %d", packageName, AppOpsManager.MODE_IGNORED));
}
Result result = runOpAndFetchResults();
......@@ -129,7 +127,7 @@ public class BatchOpsManager {
@NonNull
private Result opUninstall() {
for(String packageName: packageNames) {
for (String packageName: packageNames) {
addCommand(packageName, String.format(Locale.ROOT, "pm uninstall --user 0 %s", packageName));
}
return runOpAndFetchResults();
......
......@@ -22,7 +22,7 @@ import java.util.HashMap;
import java.util.List;
import androidx.annotation.NonNull;
import io.github.muntashirakon.AppManager.storage.StorageManager;
import io.github.muntashirakon.AppManager.storage.RulesStorageManager;
import io.github.muntashirakon.AppManager.utils.Tuple;
import io.github.muntashirakon.AppManager.utils.Utils;
......@@ -81,7 +81,7 @@ public class ExternalComponentsImporter {
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "rules");
int event = parser.nextTag();
StorageManager.Type componentType = StorageManager.Type.UNKNOWN;
RulesStorageManager.Type componentType = RulesStorageManager.Type.UNKNOWN;
String packageName = Utils.trimExtension(filename);
try (ComponentsBlocker cb = ComponentsBlocker.getMutableInstance(context, packageName)) {
String name;
......@@ -125,7 +125,7 @@ public class ExternalComponentsImporter {
throws Exception {
try {
String jsonString = Utils.getFileContent(context.getContentResolver(), uri);
HashMap<String, HashMap<String, StorageManager.Type>> packageComponents = new HashMap<>();
HashMap<String, HashMap<String, RulesStorageManager.Type>> packageComponents = new HashMap<>();
HashMap<String, PackageInfo> packageInfoList = new HashMap<>();
PackageManager packageManager = context.getPackageManager();
JSONObject jsonObject = new JSONObject(jsonString);
......@@ -151,7 +151,7 @@ public class ExternalComponentsImporter {
}
if (packageComponents.size() > 0) {
for (String packageName: packageComponents.keySet()) {
HashMap<String, StorageManager.Type> disabledComponents = packageComponents.get(packageName);
HashMap<String, RulesStorageManager.Type> disabledComponents = packageComponents.get(packageName);
//noinspection ConstantConditions
if (disabledComponents.size() > 0) {
try (ComponentsBlocker cb = ComponentsBlocker.getMutableInstance(context, packageName)){
......@@ -170,15 +170,15 @@ public class ExternalComponentsImporter {
}
}
private static StorageManager.Type getType(@NonNull String name, @NonNull PackageInfo packageInfo) {
private static RulesStorageManager.Type getType(@NonNull String name, @NonNull PackageInfo packageInfo) {
for (ActivityInfo activityInfo: packageInfo.activities)
if (activityInfo.name.equals(name)) return StorageManager.Type.ACTIVITY;
if (activityInfo.name.equals(name)) return RulesStorageManager.Type.ACTIVITY;
for (ProviderInfo providerInfo: packageInfo.providers)
if (providerInfo.name.equals(name)) return StorageManager.Type.PROVIDER;
if (providerInfo.name.equals(name)) return RulesStorageManager.Type.PROVIDER;
for (ActivityInfo receiverInfo: packageInfo.receivers)
if (receiverInfo.name.equals(name)) return StorageManager.Type.RECEIVER;
if (receiverInfo.name.equals(name)) return RulesStorageManager.Type.RECEIVER;
for (ServiceInfo serviceInfo: packageInfo.services)
if (serviceInfo.name.equals(name)) return StorageManager.Type.SERVICE;
return StorageManager.Type.UNKNOWN;
if (serviceInfo.name.equals(name)) return RulesStorageManager.Type.SERVICE;
return RulesStorageManager.Type.UNKNOWN;
}
}
......@@ -15,7 +15,7 @@ import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import org.w3c.dom.Text;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
......@@ -24,12 +24,11 @@ import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.activities.AppUsageActivity;
import io.github.muntashirakon.AppManager.usage.AppUsageStatsManager;
import io.github.muntashirakon.AppManager.utils.Utils;
public class AppUsageDetailsDialogFragment extends DialogFragment {
public static final String TAG = "AppUsageDetailsDialogFragment";
......@@ -52,7 +51,7 @@ public class AppUsageDetailsDialogFragment extends DialogFragment {
AppUsageDetailsAdapter adapter = new AppUsageDetailsAdapter(getActivity());
listView.setAdapter(adapter);
adapter.setDefaultList(packageUS.entries);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext(), R.style.CustomDialog)
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext(), R.style.AppTheme_AlertDialog)
.setTitle(packageUS.packageName)
.setView(view)
.setNegativeButton(android.R.string.ok, (dialog, which) -> {
......@@ -84,7 +83,7 @@ public class AppUsageDetailsDialogFragment extends DialogFragment {
context = activity;
mLayoutInflater = activity.getLayoutInflater();
mColorTransparent = Color.TRANSPARENT;
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.SEMI_TRANSPARENT);
mColorSemiTransparent = ContextCompat.getColor(activity, R.color.semi_transparent);
}
void setDefaultList(List<AppUsageStatsManager.USEntry> list) {
......@@ -125,7 +124,7 @@ public class AppUsageDetailsDialogFragment extends DialogFragment {
} else holder = (ViewHolder) convertView.getTag();
AppUsageStatsManager.USEntry usEntry = mAdapterList.get(position);
holder.title.setText(String.format(Locale.ROOT, "%s - %s", sSimpleDateFormat.format(usEntry.startTime), sSimpleDateFormat.format(usEntry.endTime)));
holder.subtitle.setText(AppUsageActivity.formattedTime(context, usEntry.getDuration()));
holder.subtitle.setText(Utils.getFormattedDuration(context, usEntry.getDuration()));
convertView.setBackgroundColor(position % 2 == 0 ? mColorSemiTransparent : mColorTransparent);
return convertView;
}
......
......@@ -17,10 +17,11 @@ import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import io.github.muntashirakon.AppManager.R;
......@@ -171,7 +172,7 @@ public class EditPrefItemFragment extends DialogFragment {
}
}
interfaceCommunicator = (InterfaceCommunicator) getActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity(), R.style.AppTheme_AlertDialog);
builder.setView(view)
.setPositiveButton(mode == MODE_CREATE ? R.string.add_item : R.string.done, (dialog, which) -> {
PrefItem newPrefItem;
......
......@@ -22,11 +22,13 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.utils.LauncherIconCreator;
......@@ -37,40 +39,32 @@ public class EditShortcutDialogFragment extends DialogFragment {
private ActivityInfo mActivityInfo;
private PackageManager mPackageManager;
private EditText text_name;
private EditText text_package;
private EditText text_class;
private EditText text_icon;
private ImageView image_icon;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
assert getActivity() != null;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
if (getArguments() == null) return builder.create();
if (getArguments() == null) return super.onCreateDialog(savedInstanceState);
final FragmentActivity activity = requireActivity();
mActivityInfo = getArguments().getParcelable(ARG_ACTIVITY_INFO);
if (getActivity() == null) return builder.create();
mPackageManager = getActivity().getPackageManager();
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater == null) return builder.create();
mPackageManager = activity.getPackageManager();
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater == null) return super.onCreateDialog(savedInstanceState);
@SuppressLint("InflateParams")
View view = inflater.inflate(R.layout.dialog_shortcut, null);
final String activityName = (String) mActivityInfo.loadLabel(mPackageManager);
text_name = view.findViewById(R.id.shortcut_name);
text_name.setText(activityName);
text_package = view.findViewById(R.id.package_name);
EditText text_package = view.findViewById(R.id.package_name);
text_package.setText(mActivityInfo.packageName);
text_class = view.findViewById(R.id.class_name);
EditText text_class = view.findViewById(R.id.class_name);
text_class.setText(mActivityInfo.name);
text_icon = view.findViewById(R.id.insert_icon);
ComponentName activity = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
ComponentName activityComponent = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
final String[] activityIconResourceName = new String[1];
try {
activityIconResourceName[0] = mPackageManager.getResourcesForActivity(activity).getResourceName(mActivityInfo.getIconResource());
activityIconResourceName[0] = mPackageManager.getResourcesForActivity(activityComponent).getResourceName(mActivityInfo.getIconResource());
text_icon.setText(activityIconResourceName[0]);
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException ignored) {}
......@@ -99,7 +93,8 @@ public class EditShortcutDialogFragment extends DialogFragment {
dialog.show(getFragmentManager(), IconPickerDialogFragment.TAG);
});
builder.setTitle(mActivityInfo.loadLabel(mPackageManager))
return new MaterialAlertDialogBuilder(activity, R.style.AppTheme_AlertDialog)
.setTitle(mActivityInfo.loadLabel(mPackageManager))
.setView(view)
.setIcon(mActivityInfo.loadIcon(mPackageManager))
.setPositiveButton(R.string.create_shortcut, (dialog, which) -> {
......@@ -117,14 +112,14 @@ public class EditShortcutDialogFragment extends DialogFragment {
Resources resources = mPackageManager.getResourcesForApplication(pack);
int icon_resource = resources.getIdentifier(name, type, pack);
if (icon_resource != 0) {
icon = ResourcesCompat.getDrawable(resources, icon_resource, getActivity().getTheme());
icon = ResourcesCompat.getDrawable(resources, icon_resource, activity.getTheme());
} else {
icon = mPackageManager.getDefaultActivityIcon();
Toast.makeText(getActivity(), R.string.error_invalid_icon_resource, Toast.LENGTH_LONG).show();
Toast.makeText(activity, R.string.error_invalid_icon_resource, Toast.LENGTH_LONG).show();
}
} catch (PackageManager.NameNotFoundException e) {
icon = mPackageManager.getDefaultActivityIcon();
Toast.makeText(getActivity(), R.string.error_invalid_icon_resource, Toast.LENGTH_LONG).show();
Toast.makeText(activity, R.string.error_invalid_icon_resource, Toast.LENGTH_LONG).show();
} catch (Exception e) {
icon = mPackageManager.getDefaultActivityIcon();
Toast.makeText(getActivity(), R.string.error_invalid_icon_format, Toast.LENGTH_LONG).show();
......@@ -134,9 +129,7 @@ public class EditShortcutDialogFragment extends DialogFragment {
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
if (getDialog() != null) getDialog().cancel();
});
return builder.create();
}).create();
}
private Drawable getIcon(String icon_resource_string) {
......
......@@ -16,11 +16,12 @@ import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.List;
import java.util.TreeSet;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.DialogFragment;
import io.github.muntashirakon.AppManager.R;
......@@ -62,14 +63,12 @@ public class IconPickerDialogFragment extends DialogFragment {
if (getDialog() != null) getDialog().dismiss();
}
});
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
builder.setTitle(R.string.icon_picker)
return new MaterialAlertDialogBuilder(getActivity(), R.style.AppTheme_AlertDialog)
.setTitle(R.string.icon_picker)
.setView(grid)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
if (getDialog() != null) getDialog().cancel();
});
return builder.create();
}).create();
}
public interface IconPickerListener {
......
......@@ -11,14 +11,18 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
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.utils.Tuple;
......@@ -26,25 +30,48 @@ import io.github.muntashirakon.AppManager.utils.Tuple;
public class ImportExportDialogFragment extends DialogFragment {
public static final String TAG = "ImportExportDialogFragment";
private static final String MIME_JSON = "application/json";
private static final String MIME_TSV = "text/tab-separated-values";
private static final String MIME_XML = "text/xml";
private static final int RESULT_CODE_EXPORT = 849;
private static final int RESULT_CODE_IMPORT = 247;
private static final int RESULT_CODE_WATT = 711;
private static final int RESULT_CODE_BLOCKER = 459;
private Context context;
private FragmentActivity activity;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (getActivity() == null) return super.onCreateDialog(savedInstanceState);
context = getActivity();
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (getFragmentManager() == null) return super.onCreateDialog(savedInstanceState);
activity = getActivity();
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater == null) return super.onCreateDialog(savedInstanceState);
@SuppressLint("InflateParams")
View view = inflater.inflate(R.layout.dialog_settings_import_export, null);
view.findViewById(R.id.export_internal).setOnClickListener(v -> {
@SuppressLint("SimpleDateFormat")
String fileName = "app_manager_rules_export-" + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime())) + ".am.tsv";
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(MIME_TSV);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, RESULT_CODE_EXPORT);
});
view.findViewById(R.id.import_internal).setOnClickListener(v -> {
Intent intent = new Intent()
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(MIME_TSV)
.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_files)), RESULT_CODE_IMPORT);
});
view.findViewById(R.id.import_watt).setOnClickListener(v -> {
Intent intent = new Intent()
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("text/xml")
.setType(MIME_XML)
.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_files)), RESULT_CODE_WATT);
});
......@@ -52,17 +79,16 @@ public class ImportExportDialogFragment extends DialogFragment {
Intent intent = new Intent()
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("application/json")
.setType(MIME_JSON)
.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_files)), RESULT_CODE_BLOCKER);
});
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
builder.setView(view)
.setTitle(R.string.pref_import_export)
return new MaterialAlertDialogBuilder(getActivity(), R.style.AppTheme_AlertDialog)
.setView(view)
.setTitle(R.string.pref_import_export_blocking_rules)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
if (getDialog() != null) getDialog().cancel();
});
return builder.create();
}).create();
}
@Override
......@@ -77,7 +103,7 @@ public class ImportExportDialogFragment extends DialogFragment {
}
} else uriList.add(data.getData());
Tuple<Boolean, Integer> status = ExternalComponentsImporter.applyFromWatt(
context.getApplicationContext(), uriList);
activity.getApplicationContext(), uriList);
if (!status.getFirst()) { // Not failed
Toast.makeText(getContext(), R.string.the_import_was_successful,
Toast.LENGTH_LONG).show();
......@@ -97,7 +123,7 @@ public class ImportExportDialogFragment extends DialogFragment {
}
} else uriList.add(data.getData());
Tuple<Boolean, Integer> status = ExternalComponentsImporter.applyFromBlocker(
context.getApplicationContext(), uriList);
activity.getApplicationContext(), uriList);
if (!status.getFirst()) { // Not failed
Toast.makeText(getContext(), R.string.the_import_was_successful,
Toast.LENGTH_LONG).show();
......@@ -108,6 +134,28 @@ public class ImportExportDialogFragment extends DialogFragment {
}
if (getDialog() != null) getDialog().cancel();
}
} else if (requestCode == RESULT_CODE_EXPORT) {
if (data != null) {
RulesTypeSelectionDialogFragment dialogFragment = new RulesTypeSelectionDialogFragment();
Bundle args = new Bundle();
args.putInt(RulesTypeSelectionDialogFragment.ARG_MODE, RulesTypeSelectionDialogFragment.MODE_EXPORT);
args.putParcelable(RulesTypeSelectionDialogFragment.ARG_URI, data.getData());
args.putStringArrayList(RulesTypeSelectionDialogFragment.ARG_PKG, null);
dialogFragment.setArguments(args);
activity.getSupportFragmentManager().popBackStackImmediate();
dialogFragment.show(activity.getSupportFragmentManager(), RulesTypeSelectionDialogFragment.TAG);
}
} else if (requestCode == RESULT_CODE_IMPORT) {
if (data != null) {
RulesTypeSelectionDialogFragment dialogFragment = new RulesTypeSelectionDialogFragment();
Bundle args = new Bundle();
args.putInt(RulesTypeSelectionDialogFragment.ARG_MODE, RulesTypeSelectionDialogFragment.MODE_IMPORT);
args.putParcelable(RulesTypeSelectionDialogFragment.ARG_URI, data.getData());
args.putStringArrayList(RulesTypeSelectionDialogFragment.ARG_PKG, null);
dialogFragment.setArguments(args);
activity.getSupportFragmentManager().popBackStackImmediate();
dialogFragment.show(activity.getSupportFragmentManager(), RulesTypeSelectionDialogFragment.TAG);
}
}
}
}
......
package io.github.muntashirakon.AppManager.fragments;
import android.app.Dialog;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
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.storage.RulesExporter;
import io.github.muntashirakon.AppManager.storage.RulesImporter;
import io.github.muntashirakon.AppManager.storage.RulesStorageManager;
public class RulesTypeSelectionDialogFragment extends DialogFragment {
public static final String TAG = "RulesTypeSelectionDialogFragment";
public static final String ARG_MODE = "ARG_MODE"; // int
public static final String ARG_URI = "ARG_URI"; // Uri
public static final String ARG_PKG = "ARG_PKG"; // Package Names or null (for all)
@IntDef(value = {
MODE_IMPORT,
MODE_EXPORT
})
public @interface Mode {}
public static final int MODE_IMPORT = 1;
public static final int MODE_EXPORT = 2;
private FragmentActivity activity;
private Uri mUri;
private List<String> mPackages = null;
private HashSet<RulesStorageManager.Type> mSelectedTypes;
private RulesStorageManager.Type[] types = new RulesStorageManager.Type[]{
RulesStorageManager.Type.ACTIVITY,
RulesStorageManager.Type.SERVICE,
RulesStorageManager.Type.RECEIVER,
RulesStorageManager.Type.PROVIDER,
RulesStorageManager.Type.APP_OP,
RulesStorageManager.Type.PERMISSION,
};
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (getActivity() == null) return super.onCreateDialog(savedInstanceState);
if (getArguments() == null) return super.onCreateDialog(savedInstanceState);
activity = getActivity();
@Mode int mode = getArguments().getInt(ARG_MODE, MODE_EXPORT);
mPackages = getArguments().getStringArrayList(ARG_PKG);
mUri = (Uri) getArguments().get(ARG_URI);
if (mUri == null) return super.onCreateDialog(savedInstanceState);
final boolean[] checkedItems = {true, true, true, true, true, true};
mSelectedTypes = new HashSet<>(Arrays.asList(RulesStorageManager.Type.values()));
return new MaterialAlertDialogBuilder(activity, R.style.AppTheme_AlertDialog)
.setTitle(mode == MODE_IMPORT ? R.string.import_options : R.string.export_options)
.setMultiChoiceItems(R.array.rule_types, checkedItems, (dialog, which, isChecked) -> {
if (isChecked) mSelectedTypes.add(types[which]);
else mSelectedTypes.remove(types[which]);
})
.setPositiveButton(getResources().getString(mode == MODE_IMPORT ?
R.string.pref_import : R.string.pref_export), (dialog1, which) -> {
Log.d("TestImportExport", "Types: " + mSelectedTypes.toString() + "\nURI: " + mUri.toString());
if (mode == MODE_IMPORT) handleImport();
else handleExport();
})
.setNegativeButton(getResources().getString(android.R.string.cancel), null)
.create();
}
private void handleExport() {
new Thread(() -> {
try {
RulesExporter exporter = new RulesExporter(new ArrayList<>(mSelectedTypes), mPackages);
exporter.saveRules(mUri);
activity.runOnUiThread(() -> Toast.makeText(activity, R.string.the_export_was_successful, Toast.LENGTH_LONG).show());
} catch (IOException e) {
activity.runOnUiThread(() -> Toast.makeText(activity, R.string.export_failed, Toast.LENGTH_LONG).show());
}
}).start();
}
private void handleImport() {
new Thread(() -> {
try (RulesImporter importer = new RulesImporter(new ArrayList<>(mSelectedTypes))) {
importer.addRulesFromUri(mUri);
if (mPackages != null) importer.setPackagesToImport(mPackages);
importer.applyRules();
activity.runOnUiThread(() -> Toast.makeText(activity, R.string.the_import_was_successful, Toast.LENGTH_LONG).show());
} catch (IOException e) {
activity.runOnUiThread(() -> Toast.makeText(activity, R.string.import_failed, Toast.LENGTH_LONG).show());
}
}).start();
}
}
package io.github.muntashirakon.AppManager.storage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
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;
/**
* Export rules to external directory either for a single package or multiple packages.
*
* @see RulesImporter
*/
public class RulesExporter {
private Context mContext;
private @Nullable List<String> mPackagesToExport;
private @NonNull List<RulesStorageManager.Type> mTypesToExport;
public RulesExporter(@NonNull List<RulesStorageManager.Type> typesToExport, @Nullable List<String> packagesToExport) {
mContext = AppManager.getContext();
mPackagesToExport = packagesToExport;
mTypesToExport = typesToExport;
}
public void saveRules(Uri uri) throws IOException {
if (mPackagesToExport == null) mPackagesToExport = getAllPackages();
try (OutputStream outputStream = mContext.getContentResolver().openOutputStream(uri)) {
for (String packageName: mPackagesToExport) {
// Get a read-only instance
try (ComponentsBlocker cb = ComponentsBlocker.getInstance(mContext, packageName)) {
for (RulesStorageManager.Entry entry: cb.getAll()) {
if (mTypesToExport.contains(entry.type)) {
Objects.requireNonNull(outputStream).write(String.format("%s\t%s\t%s\t%s\n", packageName, entry.name, entry.type.name(), entry.extra).getBytes());
}
}
}
}
}
}
@NonNull
private List<String> getAllPackages() {
List<ApplicationInfo> applicationInfoList = mContext.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA);
List<String> packageNames = new ArrayList<>();
for (ApplicationInfo applicationInfo: applicationInfoList)
packageNames.add(applicationInfo.packageName);
return packageNames;
}
}
package io.github.muntashirakon.AppManager.storage;
import android.content.Context;
import android.net.Uri;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;