Skip to content

Commit

Permalink
1.1
Browse files Browse the repository at this point in the history
Improve loading speed
Divide apps into user and system apps
remove unnecessary dependency
Add quick extract option to the right of apps in the list
Add options on pressing list item: Extract, Launch, Uninstall, Share, (System) App Info
Add option to sort by name, first installed or last updated date
  • Loading branch information
AbdurazaaqMohammed committed Oct 12, 2024
1 parent 662b665 commit 6206404
Show file tree
Hide file tree
Showing 23 changed files with 1,084 additions and 861 deletions.
3 changes: 1 addition & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ android {
minSdk = 4
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionName = "1.1"
multiDexEnabled = true
}

Expand All @@ -34,7 +34,6 @@ android {
viewBinding = false
}
dependencies {
implementation("org.apache.commons:commons-compress:1.24.0")
implementation("com.android.support:multidex:1.0.3")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.2")
}
Expand Down
7 changes: 3 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@
>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30"
tools:ignore="ScopedStorage" />
<application
android:name="android.support.multidex.MultiDexApplication"
android:allowBackup="true"
android:icon="@drawable/apk_extractor"
android:label="@string/app_name"
android:roundIcon="@drawable/apk_extractor"
android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute">
android:theme="@style/AppTheme">
<activity
android:name="io.github.abdurazaaqmohammed.ApkExtractor.MainActivity"
android:exported="true">
Expand All @@ -27,7 +26,7 @@
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:authorities="io.github.abdurazaaqmohammed.ApkExtractor.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
Expand Down
11 changes: 3 additions & 8 deletions app/src/main/java/com/android/apksig/internal/util/Pair.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public final class Pair<A, B> {
private final A mFirst;
private final B mSecond;

private Pair(A first, B second) {
public Pair(A first, B second) {
mFirst = first;
mSecond = second;
}
Expand Down Expand Up @@ -70,12 +70,7 @@ public boolean equals(Object obj) {
return false;
}
if (mSecond == null) {
if (other.mSecond != null) {
return false;
}
} else if (!mSecond.equals(other.mSecond)) {
return false;
}
return true;
return other.mSecond == null;
} else return mSecond.equals(other.mSecond);
}
}
81 changes: 1 addition & 80 deletions app/src/main/java/com/reandroid/apk/ApkBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,10 @@

import static io.github.abdurazaaqmohammed.ApkExtractor.MainActivity.rss;

import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.text.TextUtils;
import android.widget.TextView;

import io.github.abdurazaaqmohammed.ApkExtractor.R;
import io.github.abdurazaaqmohammed.ApkExtractor.DeviceSpecsUtil;
import io.github.abdurazaaqmohammed.ApkExtractor.MainActivity;
import io.github.abdurazaaqmohammed.ApkExtractor.MismatchedSplitsException;
import com.android.apksig.apk.ApkUtils;

import com.reandroid.apkeditor.merge.LogUtil;
import com.reandroid.archive.BlockInputSource;
import com.reandroid.archive.ZipEntryMap;
Expand All @@ -37,21 +29,16 @@
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.arsc.pool.builder.StringPoolMerger;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.zip.ZipFile;

public class ApkBundle implements Closeable {
private final Map<String, ApkModule> mModulesMap;
Expand Down Expand Up @@ -170,72 +157,6 @@ public void loadApkDirectory(File dir, boolean recursive, Context context) throw
List<File> apkList = recursive ? ApkUtil.recursiveFiles(dir, ".apk") : ApkUtil.listFiles(dir, ".apk");
if(apkList.isEmpty()) throw new FileNotFoundException("No '*.apk' files in directory: " + dir);
LogUtil.logMessage("Found apk files: "+apkList.size());
int size = apkList.size();
int[] versionCodes = new int[size];
int base = -1;
for(int i = 0; i < size; i++){
File file = apkList.get(i);
try(ZipFile zf = new ZipFile(file);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
InputStream is = zf.getInputStream(zf.getEntry("AndroidManifest.xml"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) byteArrayOutputStream.write(buffer, 0, bytesRead);
versionCodes[i] = ApkUtils.getVersionCodeFromBinaryAndroidManifest(ByteBuffer.wrap(byteArrayOutputStream.toByteArray()));
if(DeviceSpecsUtil.isBaseApk(file.getName())) base = versionCodes[i];
} catch (Exception e) {
versionCodes[i] = -1;
}
}
if(base == -1) load(apkList);
List<File> mismatchedDpis = new ArrayList<>();
StringBuilder mismatchedLangs = new StringBuilder();
for(int i = 0; i < size; i++) {
if(versionCodes[i] != base) {
File f = apkList.get(i);
String name = f.getName();
LogUtil.logMessage(name + rss.getString(R.string.mismatch_base));
if(DeviceSpecsUtil.isArch(name)) throw new MismatchedSplitsException("Error: Key (the app will not run without it) split (" + name + ") has a mismatched version code.");
if(name.contains("dpi")) mismatchedDpis.add(f);
else mismatchedLangs.append(", ").append(name);
}
}

apkList.removeAll(mismatchedDpis);
boolean hasDpi = false;
for(File f : apkList) {
if(f.getName().contains("dpi")) {
hasDpi = true;
break;
}
}
if(!hasDpi && !mismatchedDpis.isEmpty()) throw new MismatchedSplitsException("Error: All DPI/resource splits selected have a mismatched version code.");
String s = mismatchedLangs.toString();
if(!TextUtils.isEmpty(s)) {
final CountDownLatch latch = new CountDownLatch(1);
TextView title = new TextView(context);
title.setText(rss.getString(R.string.warning));
title.setTextColor(MainActivity.textColor);
title.setTextSize(25);
TextView msg = new TextView(context);
msg.setText(rss.getString(R.string.mismatch, s.replaceFirst(", ", "")));
msg.setTextColor(MainActivity.textColor);
MainActivity act = ((MainActivity) context);
act.getHandler().post(() -> act.styleAlertDialog(new AlertDialog.Builder(context).setCustomTitle(title).setView(msg).setPositiveButton("OK", (dialog, which) -> {
for(String filename : s.split(", ")) {
File f = new File(dir, filename);
f.delete();
apkList.remove(f);
}
latch.countDown();
}).setNegativeButton(rss.getString(R.string.cancel), (dialog, which) -> {
act.startActivity(new Intent(act, MainActivity.class));
if(Build.VERSION.SDK_INT > 15) act.finishAffinity();
else act.finish();
latch.countDown();
}).create(), null, false, null));
latch.await();
}
load(apkList);
}

Expand Down
83 changes: 1 addition & 82 deletions app/src/main/java/com/reandroid/apkeditor/merge/Merger.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@
import android.net.Uri;

import io.github.abdurazaaqmohammed.ApkExtractor.R;
import io.github.abdurazaaqmohammed.ApkExtractor.DeviceSpecsUtil;
import io.github.abdurazaaqmohammed.ApkExtractor.MainActivity;
import io.github.abdurazaaqmohammed.ApkExtractor.MismatchedSplitsException;
import io.github.abdurazaaqmohammed.ApkExtractor.SignUtil;
import com.j256.simplezip.ZipFileInput;
import com.j256.simplezip.format.ZipFileHeader;

import com.reandroid.apk.ApkBundle;
import com.reandroid.apk.ApkModule;
import com.reandroid.apkeditor.common.AndroidManifestHelper;
Expand All @@ -43,14 +40,9 @@
import com.reandroid.arsc.value.ValueType;
import com.starry.FileUtils;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.List;

public class Merger {
Expand All @@ -61,70 +53,6 @@ public interface LogListener {
void onLog(int resID);
}

/** @noinspection ResultOfMethodCallIgnored*/
private static void extractAndLoad(Uri in, File cacheDir, Context context, List<String> splits, ApkBundle bundle) throws IOException, MismatchedSplitsException, InterruptedException {
logMessage(in.getPath());
boolean checkSplits = splits != null && !splits.isEmpty();
try (InputStream is = FileUtils.getInputStream(in, context);
ZipFileInput zis = new ZipFileInput(is)) {
ZipFileHeader header;
while ((header = zis.readFileHeader()) != null) {
String name = header.getFileName();
if (name.endsWith(".apk")) {
if ((checkSplits && splits.contains(name)))
logMessage(MainActivity.rss.getString(R.string.skipping) + name + MainActivity.rss.getString(R.string.unselected));
else {
File file = new File(cacheDir, name);
if (file.getCanonicalPath().startsWith(cacheDir.getCanonicalPath() + File.separator))
zis.readFileDataToFile(file);
else throw new IOException("Zip entry is outside of the target dir: " + name);

logMessage("Extracted " + name);
}
} else
logMessage(MainActivity.rss.getString(R.string.skipping) + name + MainActivity.rss.getString(R.string.not_apk));
}
bundle.loadApkDirectory(cacheDir, false, context);
} catch (MismatchedSplitsException m) {
throw new RuntimeException(m);
} catch (Exception e) {
// If the above failed it probably did not copy any files
// so might as well do it this way instead of trying unreliable methods to see if we need to do this
// and possibly copying the file for no reason

// Check if already copied the file earlier to get list of splits.
if (DeviceSpecsUtil.zipFile == null) {
File input = new File(FileUtils.getPath(in, context));
boolean couldNotRead = !input.canRead();
if (couldNotRead) try(InputStream is = FileUtils.getInputStream(in, context)) {
FileUtils.copyFile(is, input = new File(cacheDir, input.getName()));
}
ZipFile zf = new ZipFile(input);
extractZipFile(zf, checkSplits, splits, cacheDir);
if (couldNotRead) input.delete();
} else extractZipFile(DeviceSpecsUtil.zipFile, checkSplits, splits, cacheDir);
bundle.loadApkDirectory(cacheDir, false, context);
}
}

private static void extractZipFile(ZipFile zf, boolean checkSplits, List<String> splits, File cacheDir) throws IOException {
Enumeration<ZipArchiveEntry> entries = zf.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry zae = entries.nextElement();
String name = zae.getName();
if (name.endsWith(".apk")) {
if ((checkSplits && splits.contains(name)))
logMessage(MainActivity.rss.getString(R.string.skipping) + name + MainActivity.rss.getString(R.string.unselected));
else try (OutputStream os = FileUtils.getOutputStream(new File(cacheDir, name));
InputStream is = zf.getInputStream(zae)) {
FileUtils.copyFile(is, os);
}
} else
logMessage(MainActivity.rss.getString(R.string.skipping) + name + MainActivity.rss.getString(R.string.not_apk));
}
zf.close();
}

public static void run(ApkBundle bundle, File cacheDir, Uri out, Context context, boolean signApk) throws IOException {
logMessage("Found modules: " + bundle.getApkModuleList().size());

Expand Down Expand Up @@ -263,13 +191,4 @@ public static void run(ApkBundle bundle, File cacheDir, Uri out, Context context

public static File signedApk;

public static void run(Uri in, File cacheDir, Uri out, Context context, List<String> splits, boolean signApk) throws Exception {
logMessage(MainActivity.rss.getString(R.string.searching));
try (ApkBundle bundle = new ApkBundle()) {
if (in == null)
bundle.loadApkDirectory(cacheDir, false, context); // Multiple splits from a split apk, already copied to cache dir
else extractAndLoad(in, cacheDir, context, splits, bundle);
run(bundle, cacheDir, out, context, signApk);
}
}
}
13 changes: 4 additions & 9 deletions app/src/main/java/com/starry/FileUtils.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.starry;

import static io.github.abdurazaaqmohammed.ApkExtractor.MainActivity.doesNotHaveStoragePerm;
import static io.github.abdurazaaqmohammed.ApkExtractor.MainActivity.getOriginalFileName;

import android.annotation.SuppressLint;
import android.content.ContentUris;
Expand Down Expand Up @@ -252,16 +251,12 @@ public static String copyFileToInternalStorageAndGetPath(Uri uri, Context contex
}

public static File copyFileToInternalStorage(Uri uri, Context context) throws IOException {
File output = new File(context.getCacheDir(), getOriginalFileName(context, uri));
File output = new File(context.getCacheDir(), io.github.abdurazaaqmohammed.ApkExtractor.MainActivity.getOriginalFileName(context, uri, !uri.getPath().endsWith(".apk")));
if(output.exists() && output.length() > 999) return output;
try (OutputStream outputStream = FileUtils.getOutputStream(output); InputStream cursor = context.getContentResolver().openInputStream(uri)) {
int read;
byte[] buffers = new byte[1024];
while ((read = cursor.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
try (InputStream cursor = context.getContentResolver().openInputStream(uri)) {
copyFile(cursor, output);
return output;
}
return output;
}

private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Expand Down
Loading

0 comments on commit 6206404

Please sign in to comment.