...
 
Commits (46)
name: Debug Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone the repository
uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew assembleDebug
- name: Store generated APK file
uses: actions/upload-artifact@v1
with:
name: AppManager
path: ./app/build/outputs/apk/debug/app-debug.apk
.gradle
.idea
/crowdin.properties
/local.properties
/.idea/workspace.xml
/.idea/libraries
......
# App Manager
![Debug Build](https://github.com/MuntashirAkon/AppManager/workflows/Debug%20Build/badge.svg)
Yet another android package manager and viewer but...
......@@ -20,16 +21,23 @@ Yet another android package manager and viewer but...
- Apk files can be shared (hence the use of a provider)
- It can be used to clear app data or app cache (requires root/ADB)
- Batch operations: clear app data, disable run in background, disable/kill/uninstall apps
- One-click operations: block ads/tracker components, block components by signature, block multiple app ops
...and other minor features such as uninstalling/enabling/disabling apps, displaying app installation info, opening on F-Droid, Aurora Droid or Aurora Store.
...and other minor features such as installing/uninstalling/updating/enabling/disabling apps, displaying app installation info, opening on F-Droid, Aurora Droid or Aurora Store.
It basically combined the features of five or six apps that any tech-savy person is needed to install in order to have a “life”.
It basically combines the features of 5 or 6 apps that any tech-savvy person needs to use in order to have a life.
See the instructions page within the app for more information on how to use the app.
Until it is available in the official repo,
<a href="https://apt.izzysoft.de/fdroid/index/apk/io.github.muntashirakon.AppManager"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" alt="Get it on IzzyOnDroid" width="200dp" /></a>
Join our Telegram channel: https://t.me/AppManagerAndroid
Telegram Support Group: https://t.me/AppManagerAndroid
Telegram Update Channel: https://t.me/AppManagerChannel
Help us translate App Manager at Crowdin: https://crwd.in/android-app-manager
### Mirrors
......
......@@ -22,6 +22,15 @@ android {
throw new GradleException("Could not read version.properties!")
}
signingConfigs {
debug {
storeFile file('dev_keystore.jks')
storePassword 'kJCp!Bda#PBdN2RLK%yMK@hatq&69E'
keyPassword 'kJCp!Bda#PBdN2RLK%yMK@hatq&69E'
keyAlias 'key0'
}
}
buildTypes {
release {
minifyEnabled false
......@@ -29,6 +38,7 @@ android {
}
debug {
applicationIdSuffix '.debug'
signingConfig signingConfigs.debug
}
}
lintOptions {
......@@ -54,5 +64,5 @@ dependencies {
implementation 'com.jaredrummler:android-shell:1.0.0'
implementation 'com.tananaev:adblib:1.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
}
......@@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".AppManager"
android:allowBackup="false"
......
This diff is collapsed.
/*
* 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.utils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class IOUtils {
public static int copy(InputStream input, OutputStream output) throws IOException {
long count = copyLarge(input, output);
if (count > Integer.MAX_VALUE) {
return -1;
}
return (int) count;
}
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
static long copyLarge(@NonNull InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long count = 0;
int n;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
input.close();
if (output != null) output.close();
return count;
}
/**
* Format xml file to correct indentation ...
*/
@Nullable
public static String getProperXml(String dirtyXml) {
try {
Document document = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new InputSource(new ByteArrayInputStream(dirtyXml.getBytes(StandardCharsets.UTF_8))));
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']",
document, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); ++i) {
Node node = nodeList.item(i);
node.getParentNode().removeChild(node);
}
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
StringWriter stringWriter = new StringWriter();
StreamResult streamResult = new StreamResult(stringWriter);
transformer.transform(new DOMSource(document), streamResult);
return stringWriter.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
......@@ -27,6 +27,7 @@ import java.io.InputStream;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.muntashirakon.AppManager.utils.IOUtils;
public class UriUtils {
......@@ -46,7 +47,7 @@ public class UriUtils {
FileOutputStream fos = new FileOutputStream(f);
InputStream is = context.getContentResolver().openInputStream(uri);
if (is == null) return null;
IOUtils.copyLarge(is, fos);
IOUtils.copy(is, fos);
return f.getPath();
} catch (IOException e) {
return null;
......
package io.github.muntashirakon.AppManager;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.loader.content.AsyncTaskLoader;
import io.github.muntashirakon.AppManager.activities.MainActivity;
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;
import io.github.muntashirakon.AppManager.utils.Utils;
import java.util.ArrayList;
import java.util.List;
public class MainLoader extends AsyncTaskLoader<List<ApplicationItem>> {
private List<ApplicationItem> mData;
private PackageIntentReceiver mPackageObserver;
private PackageManager mPackageManager;
public MainLoader(Context context) {
super(context);
mPackageManager = getContext().getPackageManager();
}
@SuppressLint("PackageManagerGetSignatures")
@Override
public List<ApplicationItem> loadInBackground() {
List<ApplicationItem> itemList = new ArrayList<>();
String pName;
final boolean isRootEnabled = AppPref.isRootEnabled();
if (MainActivity.packageList != null) {
String[] aList = MainActivity.packageList.split("[\\r\\n]+");
for (String s : aList) {
ApplicationItem item = new ApplicationItem();
if (s.endsWith("*")) {
item.star = true;
pName = s.substring(0, s.length() - 1);
} else pName = s;
try {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pName, PackageManager.GET_META_DATA);
item.applicationInfo = applicationInfo;
item.label = applicationInfo.loadLabel(mPackageManager).toString();
item.date = mPackageManager.getPackageInfo(applicationInfo.packageName, 0).lastUpdateTime; // .firstInstallTime;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
item.sha = Utils.getIssuerAndAlg(mPackageManager.getPackageInfo(applicationInfo.packageName, PackageManager.GET_SIGNING_CERTIFICATES));
} else {
item.sha = Utils.getIssuerAndAlg(mPackageManager.getPackageInfo(applicationInfo.packageName, PackageManager.GET_SIGNATURES));
}
if (Build.VERSION.SDK_INT >= 26) {
item.size = (long) -1 * applicationInfo.targetSdkVersion;
}
if (isRootEnabled) {
try (ComponentsBlocker cb = ComponentsBlocker.getInstance(getContext(), pName, true)) {
item.blockedCount = cb.componentCount();
}
}
itemList.add(item);
} catch (PackageManager.NameNotFoundException ignored) {}
}
} else {
List<ApplicationInfo> applicationInfoList = mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo : applicationInfoList) {
ApplicationItem item = new ApplicationItem();
item.applicationInfo = applicationInfo;
item.star = ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
item.label = applicationInfo.loadLabel(mPackageManager).toString();
if (Build.VERSION.SDK_INT >= 26) {
item.size = (long) -1 * applicationInfo.targetSdkVersion;
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
item.sha = Utils.getIssuerAndAlg(mPackageManager.getPackageInfo(applicationInfo.packageName, PackageManager.GET_SIGNING_CERTIFICATES));
} else {
item.sha = Utils.getIssuerAndAlg(mPackageManager.getPackageInfo(applicationInfo.packageName, PackageManager.GET_SIGNATURES));
}
item.date = mPackageManager.getPackageInfo(applicationInfo.packageName, 0).lastUpdateTime; // .firstInstallTime;
} catch (PackageManager.NameNotFoundException e) {
item.date = 0L;
item.sha = new Tuple<>("?", "?");
}
if (isRootEnabled) {
try (ComponentsBlocker cb = ComponentsBlocker.getInstance(getContext(), applicationInfo.packageName, true)) {
item.blockedCount = cb.componentCount();
}
}
itemList.add(item);
}
}
return itemList;
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
@Override
public void deliverResult(List<ApplicationItem> data) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (data != null) {
onReleaseResources(data);
}
}
List<ApplicationItem> olddata = mData;
mData = data;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(data);
}
// At this point we can release the resources associated with
// 'olddata' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (olddata != null) {
onReleaseResources(olddata);
}
}
/**
* Handles a request to start the Loader.
*/
@Override
protected void onStartLoading() {
if (mData != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mData);
}
// Start watching for changes in the app data.
if (mPackageObserver == null) {
mPackageObserver = new PackageIntentReceiver(this);
}
if (takeContentChanged() || mData == null) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to cancel a load.
*/
@Override
public void onCanceled(List<ApplicationItem> data) {
super.onCanceled(data);
// At this point we can release the resources associated with 'data'
// if needed.
onReleaseResources(data);
}
/**
* Handles a request to completely reset the Loader.
*/
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'data'
// if needed.
if (mData != null) {
onReleaseResources(mData);
mData = null;
}
// Stop monitoring for changes.
if (mPackageObserver != null) {
getContext().unregisterReceiver(mPackageObserver);
mPackageObserver = null;
}
}
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
*/
@SuppressWarnings("unused")
private void onReleaseResources(List<ApplicationItem> data) {
// For a simple List<> there is nothing to do. For something
// like a Cursor, we would close it here.
}
/**
* Helper class to look for interesting changes to the installed apps
* so that the loader can be updated.
*/
public static class PackageIntentReceiver extends BroadcastReceiver {
final MainLoader mLoader;
public PackageIntentReceiver(MainLoader loader) {
mLoader = loader;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mLoader.getContext().registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
mLoader.getContext().registerReceiver(this, sdFilter);
}
@Override
public void onReceive(Context context, Intent intent) {
mLoader.onContentChanged();
}
}
}
......@@ -45,9 +45,11 @@ public class AppDetailsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
model = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()).create(AppDetailsViewModel.class);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_details);
setSupportActionBar(findViewById(R.id.toolbar));
// Get model
Intent intent = getIntent();
// Check for package name
final String packageName = intent.getStringExtra(AppDetailsActivity.EXTRA_PACKAGE_NAME);
......@@ -60,21 +62,15 @@ public class AppDetailsActivity extends AppCompatActivity {
finish();
return;
}
// Get model
model = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()).create(AppDetailsViewModel.class);
new Thread(() -> {
if (packageName != null) model.setPackageName(packageName);
else {
try {
model.setPackageUri(apkUri);
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> {
Toast.makeText(this, getString(R.string.empty_package_name), Toast.LENGTH_LONG).show();
finish();
});
return;
}
else model.setPackageUri(apkUri);
if (model.getPackageInfo() == null) {
runOnUiThread(() -> {
Toast.makeText(this, getString(R.string.failed_to_fetch_package_info_possibly_a_split_apk), Toast.LENGTH_LONG).show();
finish();
});
return;
}
ApplicationInfo applicationInfo = model.getPackageInfo().applicationInfo;
runOnUiThread(() -> {
......@@ -101,21 +97,21 @@ public class AppDetailsActivity extends AppCompatActivity {
}
TabLayout tabLayout = findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);
// Check for the existence of package
model.getIsPackageExist().observe(this, isPackageExist -> {
if (!isPackageExist) {
Toast.makeText(this, R.string.app_not_installed, Toast.LENGTH_LONG).show();
finish();
}
});
});
}).start();
// Check for the existence of package
model.getIsPackageExist().observe(this, isPackageExist -> {
if (!isPackageExist) {
Toast.makeText(this, packageName + ": " + getString(R.string.app_not_installed), Toast.LENGTH_LONG).show();
finish();
}
});
}
@Override
protected void onDestroy() {
public void finish() {
model.onCleared();
super.onDestroy();
super.finish();
}
@SuppressLint("RestrictedApi")
......
......@@ -5,8 +5,6 @@ import android.app.Activity;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
......@@ -29,7 +27,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.ProgressIndicator;
import com.google.android.material.textview.MaterialTextView;
import java.lang.ref.WeakReference;
import java.text.Collator;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
......@@ -45,6 +42,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.fragments.AppUsageDetailsDialogFragment;
import io.github.muntashirakon.AppManager.types.IconLoaderThread;
import io.github.muntashirakon.AppManager.types.ScrollSafeSwipeRefreshLayout;
import io.github.muntashirakon.AppManager.usage.AppUsageStatsManager;
import io.github.muntashirakon.AppManager.usage.Utils.IntervalType;
......@@ -56,7 +54,8 @@ 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, ScrollSafeSwipeRefreshLayout.OnRefreshListener {
public class AppUsageActivity extends AppCompatActivity implements ListView.OnItemClickListener,
ScrollSafeSwipeRefreshLayout.OnRefreshListener {
@IntDef(value = {
SORT_BY_APP_LABEL,
SORT_BY_LAST_USED,
......@@ -329,7 +328,7 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
MaterialTextView screenTime;
MaterialTextView percentUsage;
ProgressIndicator usageIndicator;
IconAsyncTask iconLoader;
IconLoaderThread iconLoader;
}
AppUsageAdapter(@NonNull Activity activity) {
......@@ -376,7 +375,7 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
if(holder.iconLoader != null) holder.iconLoader.cancel(true);
if(holder.iconLoader != null) holder.iconLoader.interrupt();
}
final AppUsageStatsManager.PackageUS packageUS = mAdapterList.get(position);
final int percentUsage = (int) (packageUS.screenTime * 100f / totalScreenTime);
......@@ -385,8 +384,8 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageUS.packageName, 0);
holder.appLabel.setText(mPackageManager.getApplicationLabel(applicationInfo));
// Set icon
holder.iconLoader = new IconAsyncTask(holder.appIcon, applicationInfo);
holder.iconLoader.execute();
holder.iconLoader = new IconLoaderThread(holder.appIcon, applicationInfo);
holder.iconLoader.start();
} catch (PackageManager.NameNotFoundException e) {
holder.appLabel.setText(packageUS.packageName);
holder.appIcon.setImageDrawable(mPackageManager.getDefaultActivityIcon());
......@@ -400,9 +399,7 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
}
String screenTimesWithTimesOpened;
// Set times opened
screenTimesWithTimesOpened = String.format(packageUS.timesOpened == 1 ?
mActivity.getString(R.string.one_time_opened)
: mActivity.getString(R.string.no_of_times_opened), packageUS.timesOpened);
screenTimesWithTimesOpened = mActivity.getResources().getQuantityString(R.plurals.no_of_times_opened, packageUS.timesOpened, packageUS.timesOpened);
// Set screen time
screenTimesWithTimesOpened += ", " + Utils.getFormattedDuration(mActivity, packageUS.screenTime);
holder.screenTime.setText(screenTimesWithTimesOpened);
......@@ -425,43 +422,5 @@ public class AppUsageActivity extends AppCompatActivity implements ListView.OnIt
holder.usageIndicator.setProgress(percentUsage);
return convertView;
}
private static class IconAsyncTask extends AsyncTask<Void, Integer, Drawable> {
private WeakReference<ImageView> imageView = null;
ApplicationInfo info;
private IconAsyncTask(ImageView pImageViewWeakReference, ApplicationInfo info) {
link(pImageViewWeakReference);
this.info = info;
}
private void link(ImageView pImageViewWeakReference) {
imageView = new WeakReference<>(pImageViewWeakReference);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (imageView.get() != null)
imageView.get().setVisibility(View.INVISIBLE);
}
@Override
protected Drawable doInBackground(Void... voids) {
if (!isCancelled())
return info.loadIcon(mPackageManager);
return null;
}
@Override
protected void onPostExecute(Drawable drawable) {
super.onPostExecute(drawable);
if (imageView.get() != null){
imageView.get().setImageDrawable(drawable);
imageView.get().setVisibility(View.VISIBLE);
}
}
}
}
}
......@@ -237,7 +237,7 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
statsMsg.append(tracker_names[i]).append("\n"); j++;
}
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(String.format(getString(R.string.trackers_and_classes), j, tracker_names.length))
.setTitle(getString(R.string.trackers_and_classes, j, tracker_names.length))
.setNegativeButton(android.R.string.ok, null)
.setMessage(statsMsg.toString()).show();
return true;
......@@ -274,14 +274,13 @@ public class ClassListingActivity extends AppCompatActivity implements SearchVie
TextView showText = new TextView(this);
int paddingSize = getResources().getDimensionPixelSize(R.dimen.padding_medium);
showText.setPadding(paddingSize, paddingSize, paddingSize, paddingSize);
showText.setText(HtmlCompat.fromHtml(String.format(getString(R.string.tested_signatures_on_classes_and_time_taken),
showText.setText(HtmlCompat.fromHtml(getString(R.string.tested_signatures_on_classes_and_time_taken,
signatures.length, totalClassesScanned, totalTimeTaken, totalIteration, foundTrackerList + foundTrackersInfo + packageInfo)
.replaceAll(" ", "&nbsp;").replaceAll("\n", "<br/>"), HtmlCompat.FROM_HTML_MODE_LEGACY));
showText.setMovementMethod(new ScrollingMovementMethod());
showText.setTextIsSelectable(true);
new MaterialAlertDialogBuilder(this, R.style.AppTheme_AlertDialog)
.setTitle(String.format(getString(R.string.trackers_and_classes),
totalTrackersFound, classList.size()))
.setTitle(getString(R.string.trackers_and_classes, totalTrackersFound, classList.size()))
.setView(showText)
.setIcon(R.drawable.ic_frost_classysharkexodus_black_24dp)
.setNegativeButton(android.R.string.ok, null)
......
......@@ -95,7 +95,7 @@ public class ManifestViewerActivity extends AppCompatActivity {
packageUri, MANIFEST_CACHE_APK);
} else archiveFilePath = packageUri.getPath();
if (archiveFilePath != null)
packageInfo = pm.getPackageArchiveInfo(archiveFilePath, 64); // PackageManager.GET_SIGNATURES (Android Bug)
packageInfo = pm.getPackageArchiveInfo(archiveFilePath, 0);
if (packageInfo != null) {
packageName = packageInfo.packageName;
final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
......@@ -105,6 +105,8 @@ public class ManifestViewerActivity extends AppCompatActivity {
setTitle(applicationInfo.loadLabel(pm));
setWrapped();
});
} else { // Possibly a split apk
runOnUiThread(this::setWrapped);
}
} else {
runOnUiThread(() -> {
......@@ -223,9 +225,9 @@ public class ManifestViewerActivity extends AppCompatActivity {
Resources mCurResources;
try {
// https://stackoverflow.com/questions/35474016/store-and-extract-map-from-android-resource-file
mCurAm = ManifestViewerActivity.this.createPackageContext(packageName,
CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE).getAssets();
mCurResources = new Resources(mCurAm, ManifestViewerActivity.this.getResources().getDisplayMetrics(), null);
mCurAm = createPackageContext(packageName, CONTEXT_IGNORE_SECURITY
| CONTEXT_INCLUDE_CODE).getAssets();
mCurResources = new Resources(mCurAm, getResources().getDisplayMetrics(), null);
xml = mCurAm.openXmlResourceParser("AndroidManifest.xml");
code = Utils.getProperXml(getXMLText(xml, mCurResources).toString());
} catch (IOException | PackageManager.NameNotFoundException e) {
......
......@@ -4,8 +4,6 @@ import android.annotation.SuppressLint;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.format.Formatter;
import android.view.Gravity;
......@@ -25,7 +23,6 @@ import android.widget.Toast;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.progressindicator.ProgressIndicator;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
......@@ -43,8 +40,9 @@ 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.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.runner.Runner;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.types.IconLoaderThread;
import io.github.muntashirakon.AppManager.utils.AppPref;
import io.github.muntashirakon.AppManager.utils.Utils;
......@@ -245,7 +243,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
MaterialButton killBtn;
MaterialButton forceStopBtn;
MaterialButton disableBackgroundRunBtn;
IconAsyncTask iconLoader;
IconLoaderThread iconLoader;
}
@Override
......@@ -266,14 +264,14 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
if (holder.iconLoader != null) holder.iconLoader.cancel(true);
if (holder.iconLoader != null) holder.iconLoader.interrupt();
}
ProcessItem processItem = mAdapterList.get(position);
ApplicationInfo applicationInfo = processItem.applicationInfo;
String processName = processItem.name;
// Load icon
holder.iconLoader = new IconAsyncTask(holder.icon, applicationInfo);
holder.iconLoader.execute();
holder.iconLoader = new IconLoaderThread(holder.icon, applicationInfo);
holder.iconLoader.start();
// Set process name
if (mConstraint != null && processName.toLowerCase(Locale.ROOT).contains(mConstraint)) {
// Highlight searched query
......@@ -287,11 +285,11 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
holder.packageName.setText(applicationInfo.packageName);
} else holder.packageName.setVisibility(View.GONE);
// Set process IDs
holder.processIds.setText(String.format(mActivity.getString(R.string.pid_and_ppid), processItem.pid, processItem.ppid));
holder.processIds.setText(mActivity.getString(R.string.pid_and_ppid, processItem.pid, processItem.ppid));
// Set memory usage
holder.memoryUsage.setText(String.format(mActivity.getString(R.string.memory_virtual_memory), Formatter.formatFileSize(mActivity, processItem.rss), Formatter.formatFileSize(mActivity, processItem.vsz)));
holder.memoryUsage.setText(mActivity.getString(R.string.memory_virtual_memory, Formatter.formatFileSize(mActivity, processItem.rss), Formatter.formatFileSize(mActivity, processItem.vsz)));
// Set user info
holder.userInfo.setText(String.format(mActivity.getString(R.string.user_and_uid), processItem.user, processItem.uid));
holder.userInfo.setText(mActivity.getString(R.string.user_and_uid, processItem.user, processItem.uid));
// Buttons
if (applicationInfo != null) {
holder.forceStopBtn.setVisibility(View.VISIBLE);
......@@ -299,7 +297,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
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());
mActivity.runOnUiThread(() -> Toast.makeText(mActivity, mActivity.getString(R.string.failed_to_stop, processName), Toast.LENGTH_LONG).show());
}
}).start());
new Thread(() -> {
......@@ -336,7 +334,7 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
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());
mActivity.runOnUiThread(() -> Toast.makeText(mActivity, mActivity.getString(R.string.failed_to_stop, processName), Toast.LENGTH_LONG).show());
}
}).start());
} else holder.killBtn.setVisibility(View.GONE);
......@@ -383,48 +381,6 @@ public class RunningAppsActivity extends AppCompatActivity implements SearchView
};
return mFilter;
}
private static class IconAsyncTask extends AsyncTask<Void, Integer, Drawable> {
private WeakReference<ImageView> imageView = null;
ApplicationInfo info;
private IconAsyncTask(ImageView pImageViewWeakReference, ApplicationInfo info) {
link(pImageViewWeakReference);
this.info = info;
}
private void link(ImageView pImageViewWeakReference) {
imageView = new WeakReference<>(pImageViewWeakReference);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (imageView.get()!=null)
imageView.get().setVisibility(View.INVISIBLE);
}
@Override
protected Drawable doInBackground(Void... voids) {
if (!isCancelled()) {
if (info != null)
return info.loadIcon(mPackageManager);
else return mPackageManager.getDefaultActivityIcon();
}
return null;
}
@Override
protected void onPostExecute(Drawable drawable) {
super.onPostExecute(drawable);
if (imageView.get()!=null){
imageView.get().setImageDrawable(drawable);
imageView.get().setVisibility(View.VISIBLE);
}
}
}
}
class ProcessRefreshingThread extends Thread {
......
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.text.Spanned;
import android.view.MenuItem;
......@@ -22,8 +20,8 @@ import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.text.HtmlCompat;
import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.fragments.ImportExportDialogFragment;
import io.github.muntashirakon.AppManager.storage.compontents.ComponentsBlocker;
import io.github.muntashirakon.AppManager.types.FullscreenDialog;
import io.github.muntashirakon.AppManager.utils.AppPref;
import io.github.muntashirakon.AppManager.utils.Utils;
......@@ -47,6 +45,7 @@ public class SettingsActivity extends AppCompatActivity {
final SwitchMaterial usageSwitcher = findViewById(R.id.usage_toggle_btn);
final View blockingView = findViewById(R.id.blocking_view);
final View importExportView = findViewById(R.id.import_view);
final TextView appThemeMsg = findViewById(R.id.app_theme_msg);
// Read pref
......@@ -58,6 +57,7 @@ public class SettingsActivity extends AppCompatActivity {
// Set changed values
rootSwitcher.setChecked(rootEnabled);
blockingView.setVisibility(rootEnabled ? View.VISIBLE : View.GONE);
importExportView.setVisibility(rootEnabled ? View.VISIBLE : View.GONE);
blockingSwitcher.setChecked(blockingEnabled);
usageSwitcher.setChecked(usageEnabled);
final String[] themes = getResources().getStringArray(R.array.themes);
......@@ -82,6 +82,7 @@ public class SettingsActivity extends AppCompatActivity {
rootSwitcher.setOnCheckedChangeListener((buttonView, isChecked) -> {
appPref.setPref(AppPref.PrefKey.PREF_ROOT_MODE_ENABLED_BOOL, isChecked);
blockingView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
importExportView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
});
blockingSwitcher.setOnCheckedChangeListener((buttonView, isChecked) -> {
appPref.setPref(AppPref.PrefKey.PREF_GLOBAL_BLOCKING_ENABLED_BOOL, isChecked);
......@@ -93,13 +94,8 @@ public class SettingsActivity extends AppCompatActivity {
appPref.setPref(AppPref.PrefKey.PREF_USAGE_ACCESS_ENABLED_BOOL, isChecked));
// Import/Export
if (AppPref.isRootEnabled()) {
findViewById(R.id.import_view).setOnClickListener(v ->
(new ImportExportDialogFragment()).show(getSupportFragmentManager(),
ImportExportDialogFragment.TAG));
} else {
findViewById(R.id.import_view).setVisibility(View.GONE);
}
importExportView.setOnClickListener(v -> new ImportExportDialogFragment()
.show(getSupportFragmentManager(), ImportExportDialogFragment.TAG));
findViewById(R.id.about_view).setOnClickListener(v -> {
View view = getLayoutInflater().inflate(R.layout.dialog_about, null);
......
......@@ -26,6 +26,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.usage.AppUsageStatsManager;
import io.github.muntashirakon.AppManager.utils.Utils;
......@@ -37,10 +38,9 @@ public class AppUsageDetailsDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null || getArguments() == null)
return super.onCreateDialog(savedInstanceState);
AppUsageStatsManager.PackageUS packageUS = getArguments().getParcelable(ARG_PACKAGE_US);
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
FragmentActivity activity = requireActivity();
AppUsageStatsManager.PackageUS packageUS = requireArguments().getParcelable(ARG_PACKAGE_US);
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (packageUS == null || inflater == null) return super.onCreateDialog(savedInstanceState);
@SuppressLint("InflateParams")
View view = inflater.inflate(R.layout.dialog_app_usage_details, null);
......@@ -48,17 +48,17 @@ public class AppUsageDetailsDialogFragment extends DialogFragment {
listView.setDividerHeight(0);
TextView emptyView = view.findViewById(android.R.id.empty);
listView.setEmptyView(emptyView);
AppUsageDetailsAdapter adapter = new AppUsageDetailsAdapter(getActivity());
AppUsageDetailsAdapter adapter = new AppUsageDetailsAdapter(activity);
listView.setAdapter(adapter);
adapter.setDefaultList(packageUS.entries);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext(), R.style.AppTheme_AlertDialog)
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity, R.style.AppTheme_AlertDialog)
.setTitle(packageUS.packageName)
.setView(view)
.setNegativeButton(android.R.string.ok, (dialog, which) -> {
if (getDialog() == null) dismiss();
});
try {
PackageManager packageManager = getActivity().getPackageManager();
PackageManager packageManager = activity.getPackageManager();
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageUS.packageName, PackageManager.GET_META_DATA);
builder.setIcon(applicationInfo.loadIcon(packageManager));
builder.setTitle(applicationInfo.loadLabel(packageManager));
......
......@@ -23,6 +23,7 @@ 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;
public class EditPrefItemFragment extends DialogFragment {
......@@ -99,18 +100,17 @@ public class EditPrefItemFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (getActivity() == null) return super.onCreateDialog(savedInstanceState);
if (getArguments() == null) return super.onCreateDialog(savedInstanceState);
FragmentActivity activity = requireActivity();
Bundle args = requireArguments();
PrefItem prefItem = args.getParcelable(ARG_PREF_ITEM);
@Mode int mode = args.getInt(ARG_MODE);
PrefItem prefItem = getArguments().getParcelable(ARG_PREF_ITEM);
@Mode int mode = getArguments().getInt(ARG_MODE);
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
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_edit_pref_item, null);
Spinner spinner = view.findViewById(R.id.type_selector_spinner);
ArrayAdapter<CharSequence> spinnerAdapter = ArrayAdapter.createFromResource(getActivity(),
ArrayAdapter<CharSequence> spinnerAdapter = ArrayAdapter.createFromResource(activity,
R.array.shared_pref_types, android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerAdapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
......@@ -171,8 +171,8 @@ public class EditPrefItemFragment extends DialogFragment {
spinner.setSelection(TYPE_STRING);
}
}
interfaceCommunicator = (InterfaceCommunicator) getActivity();
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity(), R.style.AppTheme_AlertDialog);
interfaceCommunicator = (InterfaceCommunicator) activity;
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity, R.style.AppTheme_AlertDialog);
builder.setView(view)
.setPositiveButton(mode == MODE_CREATE ? R.string.add_item : R.string.done, (dialog, which) -> {
PrefItem newPrefItem;
......
......@@ -45,9 +45,8 @@ public class EditShortcutDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (getArguments() == null) return super.onCreateDialog(savedInstanceState);
final FragmentActivity activity = requireActivity();
mActivityInfo = getArguments().getParcelable(ARG_ACTIVITY_INFO);
mActivityInfo = requireArguments().getParcelable(ARG_ACTIVITY_INFO);
mPackageManager = activity.getPackageManager();
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater == null) return super.onCreateDialog(savedInstanceState);
......
......@@ -35,12 +35,10 @@ public class IconPickerDialogFragment extends DialogFragment {
@Override
public void onAttach(@NonNull Context activity) {
super.onAttach(activity);
this.adapter = new IconListingAdapter(activity);
adapter = new IconListingAdapter(activity);
new Thread(() -> {
IconPickerDialogFragment.this.adapter.resolve();
if (getActivity() != null) {
getActivity().runOnUiThread(() -> IconPickerDialogFragment.this.adapter.notifyDataSetChanged());
}
adapter.resolve();
requireActivity().runOnUiThread(() -> adapter.notifyDataSetChanged());
}).start();
}
......@@ -52,8 +50,7 @@ public class IconPickerDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (getActivity() == null) return super.onCreateDialog(savedInstanceState);
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutInflater inflater = (LayoutInflater) requireActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater == null) return super.onCreateDialog(savedInstanceState);
GridView grid = (GridView) inflater.inflate(R.layout.dialog_icon_picker, null);
grid.setAdapter(adapter);
......@@ -63,7 +60,7 @@ public class IconPickerDialogFragment extends DialogFragment {
if (getDialog() != null) getDialog().dismiss();
}
});
return new MaterialAlertDialogBuilder(getActivity(), R.style.AppTheme_AlertDialog)
return new MaterialAlertDialogBuilder(requireActivity(), R.style.AppTheme_AlertDialog)
.setTitle(R.string.icon_picker)
.setView(grid)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
......
......@@ -5,8 +5,11 @@ import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
......@@ -17,7 +20,6 @@ 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;
......@@ -44,9 +46,7 @@ public class ImportExportDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (getActivity() == null) return super.onCreateDialog(savedInstanceState);
if (getFragmentManager() == null) return super.onCreateDialog(savedInstanceState);
activity = getActivity();
activity = requireActivity();
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater == null) return super.onCreateDialog(savedInstanceState);
@SuppressLint("InflateParams")
......@@ -59,6 +59,7 @@ public class ImportExportDialogFragment extends DialogFragment {
intent.setType(MIME_TSV);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, RESULT_CODE_EXPORT);
dismiss();
});
view.findViewById(R.id.import_internal).setOnClickListener(v -> {
Intent intent = new Intent()
......@@ -66,7 +67,27 @@ public class ImportExportDialogFragment extends DialogFragment {
.setType(MIME_TSV)
.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_files)), RESULT_CODE_IMPORT);
dismiss();
});
view.findViewById(R.id.import_existing).setOnClickListener(v -> new Thread(() -> {
List<String> packageList = new ArrayList<>();
Handler handler = new Handler(Looper.getMainLooper());
for (ApplicationInfo applicationInfo: requireContext().getPackageManager().getInstalledApplications(0)) {
packageList.add(applicationInfo.packageName);
}
List<String> failedPackages = ExternalComponentsImporter.applyFromExistingBlockList(requireContext(), packageList);
dismiss();
if (failedPackages.isEmpty()) {
handler.post(() -> Toast.makeText(requireContext(), R.string.the_import_was_successful, Toast.LENGTH_SHORT).show());
} else {
handler.post(() ->
new MaterialAlertDialogBuilder(requireContext(), R.style.AppTheme_AlertDialog)
.setTitle(getResources().getQuantityString(R.plurals.failed_to_import_files, failedPackages.size(), failedPackages.size()))
.setItems((CharSequence[]) failedPackages.toArray(), null)
.setNegativeButton(android.R.string.ok, null)
.show());
}
}).start());
view.findViewById(R.id.import_watt).setOnClickListener(v -> {
Intent intent = new Intent()
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
......@@ -74,6 +95,7 @@ public class ImportExportDialogFragment extends DialogFragment {
.setType(MIME_XML)
.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_files)), RESULT_CODE_WATT);
dismiss();
});
view.findViewById(R.id.import_blocker).setOnClickListener(v -> {
Intent intent = new Intent()
......@@ -82,13 +104,13 @@ public class ImportExportDialogFragment extends DialogFragment {
.setType(MIME_JSON)
.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_files)), RESULT_CODE_BLOCKER);
dismiss();
});
return new MaterialAlertDialogBuilder(getActivity(), R.style.AppTheme_AlertDialog)
return new MaterialAlertDialogBuilder(activity, 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();
}).create();
.setNegativeButton(android.R.string.cancel, null)
.create();
}
@Override
......@@ -108,9 +130,9 @@ public class ImportExportDialogFragment extends DialogFragment {
Toast.makeText(getContext(), R.string.the_import_was_successful,
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getContext(), String.format(Locale.getDefault(),
getString(R.string.failed_to_import_files), status.getSecond()),
Toast.LENGTH_LONG).show();
Toast.makeText(getContext(), getResources().getQuantityString(
R.plurals.failed_to_import_files, status.getSecond(), status
.getSecond()), Toast.LENGTH_LONG).show();
}
if (getDialog() != null) getDialog().cancel();
}
......@@ -128,9 +150,9 @@ public class ImportExportDialogFragment extends DialogFragment {
Toast.makeText(getContext(), R.string.the_import_was_successful,
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getContext(), String.format(Locale.getDefault(),
getString(R.string.failed_to_import_files), status.getSecond()),
Toast.LENGTH_LONG).show();
Toast.makeText(getContext(), getResources().getQuantityString(
R.plurals.failed_to_import_files, status.getSecond(), status
.getSecond()), Toast.LENGTH_LONG).show();
}
if (getDialog() != null) getDialog().cancel();
}
......
......@@ -53,14 +53,14 @@ public class RulesTypeSelectionDialogFragment extends DialogFragment {
@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);
activity = requireActivity();
Bundle args = requireArguments();
@Mode int mode = args.getInt(ARG_MODE, MODE_EXPORT);
mPackages = args.getStringArrayList(ARG_PKG);
mUri = (Uri) args.get(ARG_URI);
if (mUri == null) return super.onCreateDialog(savedInstanceState);
final boolean[] checkedItems = {true, true, true, true, true, true};
final boolean[] checkedItems = new boolean[6];
Arrays.fill(checkedItems, 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)
......
package io.github.muntashirakon.AppManager.fragments;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.utils.ApkWhatsNewFinder;
public class WhatsNewDialogFragment extends DialogFragment {
public static final String TAG = "WhatsNewDialogFragment";
public static final String ARG_NEW_PKG_INFO = "ARG_NEW_PKG_INFO";
public static final String ARG_OLD_PKG_INFO = "ARG_OLD_PKG_INFO";
FragmentActivity activity;
WhatsNewRecyclerAdapter adapter;
PackageInfo newPkgInfo;
PackageInfo oldPkgInfo;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
activity = requireActivity();
newPkgInfo = (PackageInfo) requireArguments().get(ARG_NEW_PKG_INFO);
oldPkgInfo = (PackageInfo) requireArguments().get(ARG_OLD_PKG_INFO);
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_whats_new, null);
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(activity));
adapter = new WhatsNewRecyclerAdapter();
recyclerView.setAdapter(adapter);
new Thread(() -> {
ApkWhatsNewFinder.Change[][] changes = ApkWhatsNewFinder.getInstance().getWhatsNew(newPkgInfo, oldPkgInfo);
List<ApkWhatsNewFinder.Change> changeList = new ArrayList<>();
for (ApkWhatsNewFinder.Change[] changes1: changes) {
if (changes1.length > 0) Collections.addAll(changeList, changes1);
}
activity.runOnUiThread(() -> adapter.setAdapterList(changeList));
}).start();
return new MaterialAlertDialogBuilder(activity, R.style.AppTheme_AlertDialog)
.setTitle(R.string.whats_new)
.setView(view)
.setNegativeButton(android.R.string.ok, null)
.create();
}
class WhatsNewRecyclerAdapter extends RecyclerView.Adapter<WhatsNewRecyclerAdapter.ViewHolder> {