Stop using platform provided DownloadManager

This commit is contained in:
topjohnwu
2018-12-02 04:47:57 -05:00
parent 2a2e1236fc
commit 0241a50c6f
35 changed files with 536 additions and 525 deletions

View File

@@ -61,13 +61,14 @@ public class Const {
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
public static final int DTBO_NOTIFICATION_ID = 7;
public static final int DOWNLOAD_PROGRESS_ID = 8;
public static final String NOTIFICATION_CHANNEL = "magisk_notification";
}
public static class Url {
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json";
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json";
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d";
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed";
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
public static final String PAYPAL_URL = "https://www.paypal.me/topjohnwu";
@@ -92,7 +93,7 @@ public class Const {
// intents
public static final String OPEN_SECTION = "section";
public static final String INTENT_SET_FILENAME = "filename";
public static final String INTENT_SET_NAME = "filename";
public static final String INTENT_SET_LINK = "link";
public static final String FLASH_ACTION = "action";
public static final String FLASH_SET_BOOT = "boot";

View File

@@ -32,7 +32,7 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Void> {
private void dlSnet() throws Exception {
Shell.sh("rm -rf " + dexPath.getParent()).exec();
dexPath.getParentFile().mkdir();
HttpURLConnection conn = WebService.mustRequest(Data.snetLink, null);
HttpURLConnection conn = WebService.mustRequest(Data.snetLink);
try (
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
InputStream in = new BufferedInputStream(conn.getInputStream())) {

View File

@@ -5,9 +5,9 @@ import android.os.AsyncTask;
import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.utils.NotificationMgr;
import com.topjohnwu.magisk.utils.Notifications;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.WebService;
import com.topjohnwu.magisk.utils.Utils;
import org.json.JSONException;
import org.json.JSONObject;
@@ -46,13 +46,13 @@ public class CheckUpdates {
String jsonStr = "";
switch (Data.updateChannel) {
case Const.Value.STABLE_CHANNEL:
jsonStr = WebService.getString(Const.Url.STABLE_URL);
jsonStr = Utils.dlString(Const.Url.STABLE_URL);
break;
case Const.Value.BETA_CHANNEL:
jsonStr = WebService.getString(Const.Url.BETA_URL);
jsonStr = Utils.dlString(Const.Url.BETA_URL);
break;
case Const.Value.CUSTOM_CHANNEL:
jsonStr = WebService.getString(Data.MM().prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
jsonStr = Utils.dlString(Data.MM().prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
break;
}
@@ -89,9 +89,9 @@ public class CheckUpdates {
fetchUpdates();
if (cb != null) {
if (BuildConfig.VERSION_CODE < Data.remoteManagerVersionCode) {
NotificationMgr.managerUpdate();
Notifications.managerUpdate();
} else if (Data.magiskVersionCode < Data.remoteMagiskVersionCode) {
NotificationMgr.magiskUpdate();
Notifications.magiskUpdate();
}
cb.run();
}

View File

@@ -136,7 +136,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
if (!ShellUtils.checkSum("MD5", zip, Data.magiskMD5)) {
console.add("- Downloading zip");
HttpURLConnection conn = WebService.mustRequest(Data.magiskLink, null);
HttpURLConnection conn = WebService.mustRequest(Data.magiskLink);
buf = new BufferedInputStream(new ProgressStream(conn), conn.getContentLength());
buf.mark(conn.getContentLength() + 1);
try (OutputStream out = new FileOutputStream(zip)) {

View File

@@ -6,7 +6,7 @@ import android.webkit.WebView;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.WebService;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.ShellUtils;
import org.commonmark.node.Node;
@@ -43,7 +43,7 @@ public class MarkDownWindow extends ParallelTask<Void, Void, String> {
MagiskManager mm = Data.MM();
String md;
if (mUrl != null) {
md = WebService.getString(mUrl);
md = Utils.dlString(mUrl);
} else {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ShellUtils.pump(is, out);

View File

@@ -93,7 +93,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
if (activity == null) return null;
try {
// Request zip from Internet
HttpURLConnection conn = WebService.mustRequest(mRepo.getZipUrl(), null);
HttpURLConnection conn = WebService.mustRequest(mRepo.getZipUrl());
total = conn.getContentLength();
// Temp files
@@ -101,7 +101,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
File temp2 = new File(temp1.getParentFile(), "2.zip");
temp1.getParentFile().mkdir();
// First download the zip, Web -> temp1
// First upgrade the zip, Web -> temp1
try (
InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream()));
OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1))

View File

@@ -3,14 +3,15 @@ package com.topjohnwu.magisk.asyncs;
import android.database.Cursor;
import android.os.AsyncTask;
import com.androidnetworking.AndroidNetworking;
import com.androidnetworking.common.ANRequest;
import com.androidnetworking.common.ANResponse;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.container.Repo;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.WebService;
import org.json.JSONArray;
import org.json.JSONException;
@@ -22,9 +23,7 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
@@ -32,40 +31,36 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class UpdateRepos {
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, CPU_COUNT - 1);
private static final DateFormat dateFormat;
private MagiskManager mm;
private Set<String> cached;
private ExecutorService threadPool;
static {
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private MagiskManager mm;
private Set<String> cached;
private ExecutorService threadPool;
public UpdateRepos() {
mm = Data.MM();
}
private void waitTasks() {
threadPool.shutdown();
try {
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {}
while (true) {
try {
if (threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS))
break;
} catch (InterruptedException ignored) {}
}
}
private boolean loadJSON(String jsonString) throws JSONException, ParseException {
JSONArray jsonArray = new JSONArray(jsonString);
// Empty page, halt
if (jsonArray.length() == 0)
return false;
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject rawRepo = jsonArray.getJSONObject(i);
private void loadJSON(JSONArray array) throws JSONException, ParseException {
for (int i = 0; i < array.length(); i++) {
JSONObject rawRepo = array.getJSONObject(i);
String id = rawRepo.getString("name");
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
threadPool.execute(() -> {
@@ -83,42 +78,50 @@ public class UpdateRepos {
}
});
}
return true;
}
/* We sort repos by last push, which means that we only need to check whether the
* first page is updated to determine whether the online repo database is changed
*/
private boolean loadPage(int page) {
Map<String, String> header = new HashMap<>();
if (page == 0)
header.put(Const.Key.IF_NONE_MATCH, mm.prefs.getString(Const.Key.ETAG_KEY, ""));
String url = Utils.fmt(Const.Url.REPO_URL, page + 1);
ANRequest.GetRequestBuilder req = AndroidNetworking.get(Const.Url.REPO_URL)
.addQueryParameter("page", String.valueOf(page + 1));
if (page == 0) {
String etag = mm.prefs.getString(Const.Key.ETAG_KEY, null);
if (etag != null)
req.addHeaders(Const.Key.IF_NONE_MATCH, etag);
}
ANResponse<JSONArray> res = req.build().executeForJSONArray();
if (res.getOkHttpResponse().code() == HttpURLConnection.HTTP_NOT_MODIFIED)
return false;
// Current page is the last page
if (res.getResult() == null || res.getResult().length() == 0)
return true;
try {
HttpURLConnection conn = WebService.request(url, header);
// No updates
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED)
return false;
// Current page is the last page
if (!loadJSON(WebService.getString(conn)))
return true;
} catch (Exception e) {
loadJSON(res.getResult());
} catch (JSONException | ParseException e) {
// Should not happen, but if exception occurs, page load fails
return false;
}
// Update ETAG
if (page == 0) {
String etag = header.get(Const.Key.ETAG_KEY);
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etag).apply();
String etag = res.getOkHttpResponse().header(Const.Key.ETAG_KEY);
if (etag != null) {
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etag).apply();
}
}
String links = header.get(Const.Key.LINK_KEY);
String links = res.getOkHttpResponse().header(Const.Key.LINK_KEY);
return links == null || !links.contains("next") || loadPage(page + 1);
}
private boolean loadPages() {
return loadPage(0);
}
private void fullReload() {
Cursor c = mm.repoDB.getRawCursor();
while (c.moveToNext()) {
@@ -142,7 +145,7 @@ public class UpdateRepos {
cached = Collections.synchronizedSet(mm.repoDB.getRepoIDSet());
threadPool = Executors.newFixedThreadPool(CORE_POOL_SIZE);
if (loadPage(0)) {
if (loadPages()) {
waitTasks();
// The leftover cached means they are removed from online repo
mm.repoDB.removeRepo(cached);

View File

@@ -2,35 +2,37 @@ package com.topjohnwu.magisk.components;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.Toast;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.NoUIActivity;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Download;
import com.topjohnwu.magisk.utils.LocaleManager;
import com.topjohnwu.magisk.utils.Topic;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public abstract class FlavorActivity extends AppCompatActivity implements Topic.AutoSubscriber {
public abstract class BaseActivity extends AppCompatActivity implements Topic.AutoSubscriber {
public static final String INTENT_PERM = "perm_dialog";
protected static Runnable permissionGrantCallback;
static int[] EMPTY_INT_ARRAY = new int[0];
private ActivityResultListener activityResultListener;
static int[] EMPTY_INT_ARRAY = new int[0];
public MagiskManager mm;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Configuration config = base.getResources().getConfiguration();
config.setLocale(LocaleManager.locale);
applyOverrideConfiguration(config);
mm = Data.MM();
}
@Override
public int[] getSubscribedTopics() {
return EMPTY_INT_ARRAY;
@@ -41,6 +43,15 @@ public abstract class FlavorActivity extends AppCompatActivity implements Topic.
return -1;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Configuration config = base.getResources().getConfiguration();
config.setLocale(LocaleManager.locale);
applyOverrideConfiguration(config);
mm = Data.MM();
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
Topic.subscribe(this);
@@ -48,6 +59,9 @@ public abstract class FlavorActivity extends AppCompatActivity implements Topic.
setTheme(getDarkTheme());
}
super.onCreate(savedInstanceState);
String[] perms = getIntent().getStringArrayExtra(INTENT_PERM);
if (perms != null)
ActivityCompat.requestPermissions(this, perms, 0);
}
@Override
@@ -70,6 +84,34 @@ public abstract class FlavorActivity extends AppCompatActivity implements Topic.
}
}
public static void runWithPermission(Context context, String[] permissions, Runnable callback) {
boolean granted = true;
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(context, perm) != PackageManager.PERMISSION_GRANTED)
granted = false;
}
if (granted) {
Download.EXTERNAL_PATH.mkdirs();
callback.run();
} else {
// Passed in context should be an activity if not granted, need to show dialog!
permissionGrantCallback = callback;
if (!(context instanceof BaseActivity)) {
// Start NoUIActivity to show dialog
Intent intent = new Intent(context, Data.classMap.get(NoUIActivity.class));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(INTENT_PERM, permissions);
context.startActivity(intent);
} else {
ActivityCompat.requestPermissions((BaseActivity) context, permissions, 0);
}
}
}
public void runWithPermission(String[] permissions, Runnable callback) {
runWithPermission(this, permissions, callback);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (activityResultListener != null)
@@ -82,6 +124,23 @@ public abstract class FlavorActivity extends AppCompatActivity implements Topic.
super.startActivityForResult(intent, requestCode);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean grant = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED)
grant = false;
}
if (grant) {
if (permissionGrantCallback != null) {
permissionGrantCallback.run();
}
} else {
Toast.makeText(this, R.string.no_rw_storage, Toast.LENGTH_LONG).show();
}
permissionGrantCallback = null;
}
public interface ActivityResultListener {
void onActivityResult(int requestCode, int resultCode, Intent data);
}

View File

@@ -52,6 +52,6 @@ public class BaseFragment extends Fragment implements Topic.AutoSubscriber {
@Override
public int[] getSubscribedTopics() {
return FlavorActivity.EMPTY_INT_ARRAY;
return BaseActivity.EMPTY_INT_ARRAY;
}
}

View File

@@ -2,16 +2,17 @@ package com.topjohnwu.magisk.components;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.Toast;
import com.androidnetworking.AndroidNetworking;
import com.androidnetworking.error.ANError;
import com.androidnetworking.interfaces.DownloadListener;
import com.google.android.material.snackbar.Snackbar;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.utils.Download;
import com.topjohnwu.magisk.utils.Utils;
@@ -28,31 +29,10 @@ class InstallMethodDialog extends AlertDialog.Builder {
Intent intent;
switch (idx) {
case 1:
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
activity.runWithPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, () ->
activity.startActivityForResult(intent, Const.ID.SELECT_BOOT,
(requestCode, resultCode, data) -> {
if (requestCode == Const.ID.SELECT_BOOT &&
resultCode == Activity.RESULT_OK && data != null) {
Intent i = new Intent(activity, Data.classMap.get(FlashActivity.class))
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
activity.startActivity(i);
}
})
);
patchBoot(activity);
break;
case 0:
String filename = Utils.fmt("Magisk-v%s(%d).zip",
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
Download.receive(activity, new DownloadReceiver() {
@Override
public void onDownloadDone(Context context, Uri uri) {
SnackbarMaker.showUri(activity, uri);
}
}, Data.magiskLink, filename);
downloadOnly(activity);
break;
case 2:
intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
@@ -60,20 +40,65 @@ class InstallMethodDialog extends AlertDialog.Builder {
activity.startActivity(intent);
break;
case 3:
new CustomAlertDialog(activity)
.setTitle(R.string.warning)
.setMessage(R.string.install_inactive_slot_msg)
.setCancelable(true)
.setPositiveButton(R.string.yes, (d, i) -> {
Intent it = new Intent(activity, Data.classMap.get(FlashActivity.class))
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
activity.startActivity(it);
})
.setNegativeButton(R.string.no_thanks, null)
.show();
installInactiveSlot(activity);
break;
default:
}
});
}
private void patchBoot(BaseActivity a) {
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
a.runWithPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, () ->
a.startActivityForResult(intent, Const.ID.SELECT_BOOT,
(requestCode, resultCode, data) -> {
if (requestCode == Const.ID.SELECT_BOOT &&
resultCode == Activity.RESULT_OK && data != null) {
Intent i = new Intent(a, Data.classMap.get(FlashActivity.class))
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
a.startActivity(i);
}
})
);
}
private void downloadOnly(BaseActivity a) {
a.runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, () -> {
String filename = Utils.fmt("Magisk-v%s(%d).zip",
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
NotificationProgress progress = new NotificationProgress(filename);
AndroidNetworking
.download(Data.magiskLink, Download.EXTERNAL_PATH.getPath(), filename)
.build()
.setDownloadProgressListener(progress)
.startDownload(new DownloadListener() {
@Override
public void onDownloadComplete() {
progress.defaultDone();
SnackbarMaker.make(a,
a.getString(R.string.internal_storage, "/Download/" + filename),
Snackbar.LENGTH_LONG).show();
}
@Override
public void onError(ANError anError) {}
});
});
}
private void installInactiveSlot(BaseActivity activity) {
new CustomAlertDialog(activity)
.setTitle(R.string.warning)
.setMessage(R.string.install_inactive_slot_msg)
.setCancelable(true)
.setPositiveButton(R.string.yes, (d, i) -> {
Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
activity.startActivity(intent);
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
}

View File

@@ -1,15 +1,12 @@
package com.topjohnwu.magisk.components;
import android.Manifest;
import android.content.Intent;
import android.text.TextUtils;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
import com.topjohnwu.magisk.receivers.ManagerUpdate;
import com.topjohnwu.magisk.utils.DlInstallManager;
import com.topjohnwu.magisk.utils.Utils;
import androidx.annotation.NonNull;
@@ -19,19 +16,13 @@ public class ManagerInstallDialog extends CustomAlertDialog {
public ManagerInstallDialog(@NonNull BaseActivity activity) {
super(activity);
MagiskManager mm = Data.MM();
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
String name = Utils.fmt("MagiskManager v%s(%d)",
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)));
setMessage(mm.getString(R.string.repo_install_msg, filename));
setMessage(mm.getString(R.string.repo_install_msg, name));
setCancelable(true);
setPositiveButton(R.string.install, (d, i) -> activity.runWithPermission(
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
Intent intent = new Intent(mm, Data.classMap.get(ManagerUpdate.class));
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
mm.sendBroadcast(intent);
}))
.setNegativeButton(R.string.no_thanks, null);
setPositiveButton(R.string.install, (d, i) -> DlInstallManager.upgrade(name));
setNegativeButton(R.string.no_thanks, null);
if (!TextUtils.isEmpty(Data.managerNoteLink)) {
setNeutralButton(R.string.app_changelog, (d, i) ->
new MarkDownWindow(activity, null, Data.managerNoteLink).exec());

View File

@@ -0,0 +1,52 @@
package com.topjohnwu.magisk.components;
import android.widget.Toast;
import com.androidnetworking.interfaces.DownloadProgressListener;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.utils.Notifications;
import com.topjohnwu.magisk.utils.Utils;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
public class NotificationProgress implements DownloadProgressListener {
private NotificationManagerCompat mgr;
private NotificationCompat.Builder builder;
private long prevTime;
public NotificationProgress(String title) {
mgr = NotificationManagerCompat.from(Data.MM());
builder = Notifications.progress(title);
mgr.notify(Const.ID.DOWNLOAD_PROGRESS_ID, builder.build());
prevTime = System.currentTimeMillis();
Utils.toast("Downloading " + title, Toast.LENGTH_SHORT);
}
@Override
public void onProgress(long bytesDownloaded, long totalBytes) {
long cur = System.currentTimeMillis();
if (cur - prevTime >= 1000) {
prevTime = cur;
builder.setProgress((int) totalBytes, (int) bytesDownloaded, false);
builder.setContentText(bytesDownloaded * 100 / totalBytes + "%");
update();
}
}
public NotificationCompat.Builder getBuilder() {
return builder;
}
public void update() {
mgr.notify(Const.ID.DOWNLOAD_PROGRESS_ID, builder.build());
}
public void defaultDone() {
builder.setProgress(0, 0, false);
builder.setContentText("Download done");
update();
}
}

View File

@@ -2,29 +2,29 @@ package com.topjohnwu.magisk.components;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.widget.Toast;
import com.androidnetworking.AndroidNetworking;
import com.androidnetworking.error.ANError;
import com.androidnetworking.interfaces.DownloadListener;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.utils.Download;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import java.io.File;
import androidx.annotation.NonNull;
public class UninstallDialog extends CustomAlertDialog {
public UninstallDialog(@NonNull Activity activity) {
super(activity);
MagiskManager mm = Data.MM();
setTitle(R.string.uninstall_magisk_title);
setMessage(R.string.uninstall_magisk_msg);
setNeutralButton(R.string.restore_img, (d, i) -> {
@@ -41,17 +41,27 @@ public class UninstallDialog extends CustomAlertDialog {
});
});
if (!TextUtils.isEmpty(Data.uninstallerLink)) {
setPositiveButton(R.string.complete_uninstall, (d, i) ->
Download.receive(activity, new DownloadReceiver() {
setPositiveButton(R.string.complete_uninstall, (d, i) -> {
File zip = new File(activity.getFilesDir(), "uninstaller.zip");
NotificationProgress progress = new NotificationProgress(zip.getName());
AndroidNetworking.download(Data.uninstallerLink, zip.getParent(), zip.getName())
.build()
.setDownloadProgressListener(progress)
.startDownload(new DownloadListener() {
@Override
public void onDownloadDone(Context context, Uri uri) {
Intent intent = new Intent(context, Data.classMap.get(FlashActivity.class))
public void onDownloadComplete() {
progress.defaultDone();
Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(uri)
.setData(Uri.fromFile(zip))
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
context.startActivity(intent);
activity.startActivity(intent);
}
}, Data.uninstallerLink, "magisk-uninstaller.zip"));
@Override
public void onError(ANError anError) {}
});
});
}
}
}

View File

@@ -7,7 +7,6 @@ import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.utils.Download;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.WebService;
import java.text.DateFormat;
import java.util.Date;
@@ -26,7 +25,7 @@ public class Repo extends BaseModule {
}
public void update() throws IllegalRepoException {
String props[] = Utils.dos2unix(WebService.getString(getManifestUrl())).split("\\n");
String props[] = Utils.dlString(getPropUrl()).split("\\n");
try {
parseProps(props);
} catch (NumberFormatException e) {
@@ -57,7 +56,7 @@ public class Repo extends BaseModule {
return String.format(Const.Url.ZIP_URL, getId());
}
public String getManifestUrl() {
public String getPropUrl() {
return String.format(Const.Url.FILE_URL, getId(), "module.prop");
}

View File

@@ -118,7 +118,7 @@ public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
Shell.su("/system/bin/reboot bootloader").submit();
return true;
case R.id.reboot_download:
Shell.su("/system/bin/reboot download").submit();
Shell.su("/system/bin/reboot upgrade").submit();
return true;
default:
return false;

View File

@@ -1,9 +1,7 @@
package com.topjohnwu.magisk.fragments;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -18,15 +16,13 @@ import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.PatchAPK;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.utils.DlInstallManager;
import com.topjohnwu.magisk.utils.Download;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.LocaleManager;
import com.topjohnwu.magisk.utils.RootUtils;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
import java.io.IOException;
import java.util.Locale;
@@ -85,23 +81,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
});
Preference restoreManager = findPreference("restore");
restoreManager.setOnPreferenceClickListener(pref -> {
Download.receive(
requireActivity(), new DownloadReceiver() {
@Override
public void onDownloadDone(Context context, Uri uri) {
Data.exportPrefs();
Shell.su("cp " + uri.getPath() + " /data/local/tmp/manager.apk").exec();
if (ShellUtils.fastCmdResult("pm install /data/local/tmp/manager.apk")) {
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
RootUtils.rmAndLaunch(context.getPackageName(), Const.ORIG_PKG_NAME);
return;
}
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
}
},
Data.managerLink,
Utils.fmt("MagiskManager-v%s.apk", Data.remoteManagerVersionString)
);
DlInstallManager.restore();
return true;
});
findPreference("clear").setOnPreferenceClickListener(pref -> {

View File

@@ -3,47 +3,16 @@ package com.topjohnwu.magisk.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.asyncs.PatchAPK;
import com.topjohnwu.magisk.utils.Download;
import com.topjohnwu.utils.JarMap;
import com.topjohnwu.utils.SignAPK;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.utils.DlInstallManager;
public class ManagerUpdate extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Download.receive(
context, new PatchedInstall(),
intent.getStringExtra(Const.Key.INTENT_SET_LINK),
intent.getStringExtra(Const.Key.INTENT_SET_FILENAME)
);
}
private static class PatchedInstall extends ManagerInstall {
@Override
public void onDownloadDone(Context context, Uri uri) {
if (!context.getPackageName().equals(Const.ORIG_PKG_NAME)) {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
String orig = uri.getPath();
String patch = orig.substring(0, orig.lastIndexOf('.')) + "-patched.apk";
try {
JarMap apk = new JarMap(orig);
PatchAPK.patchPackageID(apk, Const.ORIG_PKG_NAME, context.getPackageName());
SignAPK.sign(apk, new BufferedOutputStream(new FileOutputStream(patch)));
super.onDownloadDone(context, Uri.fromFile(new File(patch)));
} catch (Exception ignored) { }
});
} else {
super.onDownloadDone(context, uri);
}
}
Data.managerLink = intent.getStringExtra(Const.Key.INTENT_SET_LINK);
DlInstallManager.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME));
}
}

View File

@@ -5,7 +5,7 @@ import android.content.Intent;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.utils.NotificationMgr;
import com.topjohnwu.magisk.utils.Notifications;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
@@ -28,6 +28,6 @@ public class OnBootService extends JobIntentService {
* to reboot if dtbo wasn't patched and patched by Magisk Manager.
* */
if (Shell.rootAccess() && ShellUtils.fastCmdResult("mm_patch_dtbo"))
NotificationMgr.dtboPatched();
Notifications.dtboPatched();
}
}

View File

@@ -0,0 +1,101 @@
package com.topjohnwu.magisk.utils;
import android.os.AsyncTask;
import com.androidnetworking.AndroidNetworking;
import com.androidnetworking.error.ANError;
import com.androidnetworking.interfaces.DownloadListener;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.asyncs.PatchAPK;
import com.topjohnwu.magisk.components.NotificationProgress;
import com.topjohnwu.superuser.ShellUtils;
import com.topjohnwu.utils.JarMap;
import com.topjohnwu.utils.SignAPK;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class DlInstallManager {
public static void upgrade(String name) {
dlInstall(name, new PatchPackageName());
}
public static void restore() {
String name = Utils.fmt("MagiskManager v%s(%d)",
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
dlInstall(name, new RestoreManager());
}
public static void dlInstall(String name, ManagerDownloadListener listener) {
MagiskManager mm = Data.MM();
File apk = new File(mm.getFilesDir(), "manager.apk");
NotificationProgress progress = new NotificationProgress(name);
listener.setInstances(apk, progress);
AndroidNetworking
.download(Data.managerLink, apk.getParent(), apk.getName())
.setExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
.build()
.setDownloadProgressListener(progress)
.startDownload(listener);
}
public abstract static class ManagerDownloadListener implements DownloadListener {
private File apk;
private NotificationProgress progress;
private void setInstances(File apk, NotificationProgress progress) {
this.apk = apk;
this.progress = progress;
}
public abstract void onDownloadComplete(File apk, NotificationProgress progress);
@Override
public final void onDownloadComplete() {
onDownloadComplete(apk, progress);
}
@Override
public void onError(ANError anError) {}
}
private static class PatchPackageName extends ManagerDownloadListener {
@Override
public void onDownloadComplete(File apk, NotificationProgress progress) {
File patched = apk;
MagiskManager mm = Data.MM();
if (!mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
progress.getBuilder()
.setProgress(0, 0, true)
.setContentText("Patching APK");
progress.update();
patched = new File(apk.getParent(), "patched.apk");
try {
JarMap jarMap = new JarMap(apk);
PatchAPK.patchPackageID(jarMap, Const.ORIG_PKG_NAME, mm.getPackageName());
SignAPK.sign(jarMap, new BufferedOutputStream(new FileOutputStream(patched)));
} catch (Exception e) {
return;
}
}
progress.defaultDone();
APKInstall.install(mm, patched);
}
}
private static class RestoreManager extends ManagerDownloadListener {
@Override
public void onDownloadComplete(File apk, NotificationProgress progress) {
progress.defaultDone();
Data.exportPrefs();
if (ShellUtils.fastCmdResult("pm install " + apk))
RootUtils.rmAndLaunch(Data.MM().getPackageName(), Const.ORIG_PKG_NAME);
}
}
}

View File

@@ -1,8 +1,6 @@
package com.topjohnwu.magisk.utils;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import com.topjohnwu.magisk.Const;
@@ -14,9 +12,10 @@ import com.topjohnwu.magisk.receivers.ManagerUpdate;
import com.topjohnwu.magisk.receivers.RebootReceiver;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.TaskStackBuilder;
public class NotificationMgr {
public class Notifications {
public static void magiskUpdate() {
MagiskManager mm = Data.MM();
@@ -37,19 +36,18 @@ public class NotificationMgr {
.setAutoCancel(true)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
NotificationManagerCompat mgr = NotificationManagerCompat.from(mm);
mgr.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
}
public static void managerUpdate() {
MagiskManager mm = Data.MM();
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
String name = Utils.fmt("MagiskManager v%s(%d)",
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
Intent intent = new Intent(mm, Data.classMap.get(ManagerUpdate.class));
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
intent.putExtra(Const.Key.INTENT_SET_NAME, name);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -61,9 +59,8 @@ public class NotificationMgr {
.setAutoCancel(true)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
NotificationManagerCompat mgr = NotificationManagerCompat.from(mm);
mgr.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
}
public static void dtboPatched() {
@@ -80,8 +77,16 @@ public class NotificationMgr {
.setVibrate(new long[]{0, 100, 100, 100})
.addAction(R.drawable.ic_refresh, mm.getString(R.string.reboot), pendingIntent);
NotificationManager notificationManager =
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
NotificationManagerCompat mgr = NotificationManagerCompat.from(mm);
mgr.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
}
public static NotificationCompat.Builder progress(String title) {
MagiskManager mm = Data.MM();
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
builder.setSmallIcon(R.drawable.ic_magisk_outline)
.setContentTitle(title)
.setProgress(0, 0, true);
return builder;
}
}

View File

@@ -16,6 +16,7 @@ import android.os.AsyncTask;
import android.provider.OpenableColumns;
import android.widget.Toast;
import com.androidnetworking.AndroidNetworking;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.MagiskManager;
@@ -150,4 +151,9 @@ public class Utils {
return Shell.rootAccess() && (Const.USER_ID == 0 ||
Data.multiuserState != Const.Value.MULTIUSER_MODE_OWNER_MANAGED);
}
public static String dlString(String url) {
String s = (String) AndroidNetworking.get(url).build().executeForString().getResult();
return s == null ? "" : s;
}
}