diff --git a/app/build.gradle b/app/build.gradle index a3561c69d..bc9f8a8e1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -70,8 +70,9 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - fullImplementation project(':utils') implementation 'androidx.core:core:1.0.1' + fullImplementation project(':utils') + fullImplementation 'com.amitshekhar.android:android-networking:1.0.2' fullImplementation 'androidx.appcompat:appcompat:1.0.2' fullImplementation "androidx.preference:preference:${rootProject.ext.androidXVersion}" fullImplementation "androidx.recyclerview:recyclerview:${rootProject.ext.androidXVersion}" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f6f55fa8e..d25f21c4a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -25,6 +25,9 @@ # Snet extention -keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; } +# Fast Android Networking Library +-dontwarn okhttp3.** + # Strip logging -assumenosideeffects class com.topjohnwu.magisk.utils.Logger { public *** debug(...); diff --git a/app/src/main/java/a/g.java b/app/src/full/java/a/g.java similarity index 100% rename from app/src/main/java/a/g.java rename to app/src/full/java/a/g.java diff --git a/app/src/full/java/com/topjohnwu/magisk/Const.java b/app/src/full/java/com/topjohnwu/magisk/Const.java index a7d8a1584..11a15fed8 100644 --- a/app/src/full/java/com/topjohnwu/magisk/Const.java +++ b/app/src/full/java/com/topjohnwu/magisk/Const.java @@ -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"; diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java index 2dde7ee29..4ce06d544 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java @@ -32,7 +32,7 @@ public class CheckSafetyNet extends ParallelTask { 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())) { diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java index fbea511cc..f7ef578de 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java @@ -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(); } diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java index f38ce4f09..f0e2c3704 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java @@ -136,7 +136,7 @@ public class InstallMagisk extends ParallelTask { 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)) { diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/MarkDownWindow.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/MarkDownWindow.java index 65a8d5e82..76694e03e 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/MarkDownWindow.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/MarkDownWindow.java @@ -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 { 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); diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java index a62f29e96..51930cce2 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java @@ -93,7 +93,7 @@ public class ProcessRepoZip extends ParallelTask { 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 { 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)) diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java index 5aadb82f0..a07c6ab9d 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java @@ -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 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 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 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 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); diff --git a/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java b/app/src/full/java/com/topjohnwu/magisk/components/BaseActivity.java similarity index 52% rename from app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java rename to app/src/full/java/com/topjohnwu/magisk/components/BaseActivity.java index cf50c224f..71efa6278 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/FlavorActivity.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/BaseActivity.java @@ -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); } diff --git a/app/src/full/java/com/topjohnwu/magisk/components/BaseFragment.java b/app/src/full/java/com/topjohnwu/magisk/components/BaseFragment.java index 76dc76f26..c442fb462 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/BaseFragment.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/BaseFragment.java @@ -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; } } diff --git a/app/src/full/java/com/topjohnwu/magisk/components/InstallMethodDialog.java b/app/src/full/java/com/topjohnwu/magisk/components/InstallMethodDialog.java index 61345d49a..fce04e216 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/InstallMethodDialog.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/InstallMethodDialog.java @@ -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(); + } } diff --git a/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java b/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java index d1b395c08..34e942647 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/ManagerInstallDialog.java @@ -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()); diff --git a/app/src/full/java/com/topjohnwu/magisk/components/NotificationProgress.java b/app/src/full/java/com/topjohnwu/magisk/components/NotificationProgress.java new file mode 100644 index 000000000..676820bd3 --- /dev/null +++ b/app/src/full/java/com/topjohnwu/magisk/components/NotificationProgress.java @@ -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(); + } +} diff --git a/app/src/full/java/com/topjohnwu/magisk/components/UninstallDialog.java b/app/src/full/java/com/topjohnwu/magisk/components/UninstallDialog.java index 23e5fa4ae..b5de56256 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/UninstallDialog.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/UninstallDialog.java @@ -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) {} + }); + }); } } } diff --git a/app/src/full/java/com/topjohnwu/magisk/container/Repo.java b/app/src/full/java/com/topjohnwu/magisk/container/Repo.java index ffea3a08a..18fc26e3d 100644 --- a/app/src/full/java/com/topjohnwu/magisk/container/Repo.java +++ b/app/src/full/java/com/topjohnwu/magisk/container/Repo.java @@ -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"); } diff --git a/app/src/full/java/com/topjohnwu/magisk/fragments/ModulesFragment.java b/app/src/full/java/com/topjohnwu/magisk/fragments/ModulesFragment.java index f392d0fed..dbb6c4813 100644 --- a/app/src/full/java/com/topjohnwu/magisk/fragments/ModulesFragment.java +++ b/app/src/full/java/com/topjohnwu/magisk/fragments/ModulesFragment.java @@ -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; diff --git a/app/src/full/java/com/topjohnwu/magisk/fragments/SettingsFragment.java b/app/src/full/java/com/topjohnwu/magisk/fragments/SettingsFragment.java index 971b93009..a4c9323e7 100644 --- a/app/src/full/java/com/topjohnwu/magisk/fragments/SettingsFragment.java +++ b/app/src/full/java/com/topjohnwu/magisk/fragments/SettingsFragment.java @@ -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 -> { diff --git a/app/src/full/java/com/topjohnwu/magisk/receivers/ManagerUpdate.java b/app/src/full/java/com/topjohnwu/magisk/receivers/ManagerUpdate.java index d00986a12..f0d0a4428 100644 --- a/app/src/full/java/com/topjohnwu/magisk/receivers/ManagerUpdate.java +++ b/app/src/full/java/com/topjohnwu/magisk/receivers/ManagerUpdate.java @@ -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)); } } diff --git a/app/src/full/java/com/topjohnwu/magisk/services/OnBootService.java b/app/src/full/java/com/topjohnwu/magisk/services/OnBootService.java index d44cf6163..c85ec99ee 100644 --- a/app/src/full/java/com/topjohnwu/magisk/services/OnBootService.java +++ b/app/src/full/java/com/topjohnwu/magisk/services/OnBootService.java @@ -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(); } } diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/DlInstallManager.java b/app/src/full/java/com/topjohnwu/magisk/utils/DlInstallManager.java new file mode 100644 index 000000000..6c19331ea --- /dev/null +++ b/app/src/full/java/com/topjohnwu/magisk/utils/DlInstallManager.java @@ -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); + } + } +} diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/NotificationMgr.java b/app/src/full/java/com/topjohnwu/magisk/utils/Notifications.java similarity index 76% rename from app/src/full/java/com/topjohnwu/magisk/utils/NotificationMgr.java rename to app/src/full/java/com/topjohnwu/magisk/utils/Notifications.java index c41ee3809..2c052ffdf 100644 --- a/app/src/full/java/com/topjohnwu/magisk/utils/NotificationMgr.java +++ b/app/src/full/java/com/topjohnwu/magisk/utils/Notifications.java @@ -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; } } diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/full/java/com/topjohnwu/magisk/utils/Utils.java index 4433be508..29f4f874c 100644 --- a/app/src/full/java/com/topjohnwu/magisk/utils/Utils.java +++ b/app/src/full/java/com/topjohnwu/magisk/utils/Utils.java @@ -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; + } } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/components/BaseActivity.java b/app/src/main/java/com/topjohnwu/magisk/components/BaseActivity.java deleted file mode 100644 index 1957493be..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/components/BaseActivity.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.topjohnwu.magisk.components; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.widget.Toast; - -import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.utils.Download; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -public abstract class BaseActivity extends FlavorActivity { - - public static final String INTENT_PERM = "perm_dialog"; - - protected static Runnable permissionGrantCallback; - - 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 activity to show dialog - Intent intent = new Intent(context, a.g.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 onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - String[] perms = getIntent().getStringArrayExtra(INTENT_PERM); - if (perms != null) - ActivityCompat.requestPermissions(this, perms, 0); - } - - @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; - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/receivers/DownloadReceiver.java b/app/src/main/java/com/topjohnwu/magisk/receivers/DownloadReceiver.java deleted file mode 100644 index 6a283fa21..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/receivers/DownloadReceiver.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.topjohnwu.magisk.receivers; - -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.widget.Toast; - -import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.utils.Download; - -import java.io.File; - -public abstract class DownloadReceiver extends BroadcastReceiver { - protected File mFile; - private long downloadID; - - @Override - public void onReceive(Context context, Intent intent) { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - String action = intent.getAction(); - if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) { - DownloadManager.Query query = new DownloadManager.Query(); - query.setFilterById(downloadID); - Cursor c = downloadManager.query(query); - if (c.moveToFirst()) { - int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); - int status = c.getInt(columnIndex); - switch (status) { - case DownloadManager.STATUS_SUCCESSFUL: - Uri uri = Uri.parse(c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))); - onDownloadDone(context, uri); - break; - default: - Toast.makeText(context, R.string.download_file_error, Toast.LENGTH_LONG).show(); - break; - } - context.unregisterReceiver(this); - } - c.close(); - } - Download.isDownloading = false; - } - - public DownloadReceiver setDownloadID(long id) { - downloadID = id; - return this; - } - - public DownloadReceiver setFile(File file) { - mFile = file; - return this; - } - - public abstract void onDownloadDone(Context context, Uri uri); -} diff --git a/app/src/main/java/com/topjohnwu/magisk/receivers/ManagerInstall.java b/app/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java similarity index 56% rename from app/src/main/java/com/topjohnwu/magisk/receivers/ManagerInstall.java rename to app/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java index a0f881ad4..37326a44a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/receivers/ManagerInstall.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java @@ -1,4 +1,4 @@ -package com.topjohnwu.magisk.receivers; +package com.topjohnwu.magisk.utils; import android.content.Context; import android.content.Intent; @@ -9,22 +9,20 @@ import java.io.File; import androidx.core.content.FileProvider; -public class ManagerInstall extends DownloadReceiver { - @Override - public void onDownloadDone(Context context, Uri uri) { +public class APKInstall { + public static void install(Context c, File apk) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE); install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - Uri content = FileProvider.getUriForFile(context, - context.getPackageName() + ".provider", new File(uri.getPath())); + Uri content = FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk); install.setData(content); - context.startActivity(install); + c.startActivity(install); } else { Intent install = new Intent(Intent.ACTION_VIEW); - install.setDataAndType(uri, "application/vnd.android.package-archive"); + install.setDataAndType(Uri.fromFile(apk), "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(install); + c.startActivity(install); } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Download.java b/app/src/main/java/com/topjohnwu/magisk/utils/Download.java index b6265a232..6735d9f56 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Download.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Download.java @@ -1,18 +1,9 @@ package com.topjohnwu.magisk.utils; -import android.Manifest; -import android.app.DownloadManager; import android.content.Context; -import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.net.Uri; import android.os.Environment; -import android.widget.Toast; - -import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.components.BaseActivity; -import com.topjohnwu.magisk.receivers.DownloadReceiver; import java.io.File; @@ -25,33 +16,6 @@ public class Download { EXTERNAL_PATH.mkdirs(); } - public static boolean isDownloading = false; - - public static void receive(Context context, DownloadReceiver receiver, String link, String filename) { - if (isDownloading) - return; - - BaseActivity.runWithPermission(context, - new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> { - File file = new File(EXTERNAL_PATH, getLegalFilename(filename)); - file.delete(); - - Toast.makeText(context, context.getString(R.string.downloading_toast, filename), - Toast.LENGTH_LONG).show(); - - isDownloading = true; - - DownloadManager.Request request = new DownloadManager - .Request(Uri.parse(link)) - .setDestinationUri(Uri.fromFile(file)); - - DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - receiver.setDownloadID(dm.enqueue(request)).setFile(file); - context.getApplicationContext().registerReceiver(receiver, - new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); - }); - } - public static String getLegalFilename(CharSequence filename) { return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "") .replace("$", "").replace("`", "").replace("*", "").replace("/", "_") diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/WebService.java b/app/src/main/java/com/topjohnwu/magisk/utils/WebService.java index 68e29d6a2..ad5ba03f1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/WebService.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/WebService.java @@ -1,79 +1,24 @@ package com.topjohnwu.magisk.utils; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; -import java.util.List; -import java.util.Map; public class WebService { - public static String getString(String url) { - return getString(url, null); - } - - public static String getString(String url, Map header) { - try { - HttpURLConnection conn = request(url, header); - return getString(conn); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } - - public static String getString(HttpURLConnection conn) { - try { - StringBuilder builder = new StringBuilder(); - if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { - int len; - char buf[] = new char[4096]; - while ((len = br.read(buf)) != -1) { - builder.append(buf, 0, len); - } - } - } - conn.disconnect(); - return builder.toString(); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } - - public static HttpURLConnection request(String address, Map header) throws IOException { + public static HttpURLConnection request(String address) throws IOException { URL url = new URL(address); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(15000); conn.setConnectTimeout(15000); - - if (header != null) { - for (Map.Entry entry : header.entrySet()) { - conn.setRequestProperty(entry.getKey(), entry.getValue()); - } - } - conn.connect(); - - if (header != null) { - header.clear(); - for (Map.Entry> entry : conn.getHeaderFields().entrySet()) { - List l = entry.getValue(); - header.put(entry.getKey(), l.get(l.size() - 1)); - } - } - return conn; } - public static HttpURLConnection mustRequest(String address, Map header) throws IOException { + public static HttpURLConnection mustRequest(String address) throws IOException { HttpURLConnection conn; do { - conn = WebService.request(address, header); + conn = WebService.request(address); int total = conn.getContentLength(); if (total < 0) conn.disconnect(); diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index 4495c28c8..29886de5d 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -1,4 +1,5 @@ + \ No newline at end of file diff --git a/app/src/stub/AndroidManifest.xml b/app/src/stub/AndroidManifest.xml index 5f01dafef..4da855f68 100644 --- a/app/src/stub/AndroidManifest.xml +++ b/app/src/stub/AndroidManifest.xml @@ -1,10 +1,13 @@ - + @@ -12,7 +15,7 @@ - + diff --git a/app/src/stub/java/com/topjohnwu/magisk/receivers/BootLauncher.java b/app/src/stub/java/com/topjohnwu/magisk/BootLauncher.java similarity index 75% rename from app/src/stub/java/com/topjohnwu/magisk/receivers/BootLauncher.java rename to app/src/stub/java/com/topjohnwu/magisk/BootLauncher.java index bc5a7f78e..39390d026 100644 --- a/app/src/stub/java/com/topjohnwu/magisk/receivers/BootLauncher.java +++ b/app/src/stub/java/com/topjohnwu/magisk/BootLauncher.java @@ -1,17 +1,15 @@ -package com.topjohnwu.magisk.receivers; +package com.topjohnwu.magisk; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.text.TextUtils; -import com.topjohnwu.magisk.NoUIActivity; - public class BootLauncher extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) { - Intent i = new Intent(context, NoUIActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent i = new Intent(context, MainActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); } } diff --git a/app/src/stub/java/com/topjohnwu/magisk/MainActivity.java b/app/src/stub/java/com/topjohnwu/magisk/MainActivity.java new file mode 100644 index 000000000..6ad7bf6f9 --- /dev/null +++ b/app/src/stub/java/com/topjohnwu/magisk/MainActivity.java @@ -0,0 +1,105 @@ +package com.topjohnwu.magisk; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Application; +import android.os.AsyncTask; +import android.os.Bundle; + +import com.topjohnwu.magisk.utils.APKInstall; +import com.topjohnwu.magisk.utils.Download; +import com.topjohnwu.magisk.utils.WebService; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; + +public class MainActivity extends Activity { + + private static final String URL = + "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json"; + + private String apkLink; + + private void dlAPK() { + Application app = getApplication(); + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + try { + HttpURLConnection conn = WebService.request(apkLink); + File apk = new File(getFilesDir(), "manager.apk"); + try (InputStream in = new BufferedInputStream(conn.getInputStream()); + OutputStream out = new BufferedOutputStream(new FileOutputStream(apk))) { + int len; + byte[] buf = new byte[4096]; + while ((len = in.read(buf)) != -1) { + out.write(buf, 0, len); + } + } + conn.disconnect(); + APKInstall.install(app, apk); + } catch (IOException e) { + e.printStackTrace(); + } + }); + finish(); + } + + private void dlJSON() throws IOException, JSONException { + HttpURLConnection conn = WebService.request(URL); + StringBuilder builder = new StringBuilder(); + if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { + int len; + char buf[] = new char[4096]; + while ((len = br.read(buf)) != -1) { + builder.append(buf, 0, len); + } + } + } + conn.disconnect(); + JSONObject json = new JSONObject(builder.toString()); + JSONObject manager = json.getJSONObject("app"); + apkLink = manager.getString("link"); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (Download.checkNetworkStatus(this)) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + try { + dlJSON(); + runOnUiThread(() -> { + new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) + .setCancelable(false) + .setTitle(R.string.app_name) + .setMessage(R.string.upgrade_msg) + .setPositiveButton(R.string.yes, (d, w) -> dlAPK()) + .setNegativeButton(R.string.no_thanks, (d, w) -> finish()) + .show(); + }); + } catch (JSONException | IOException e) { + finish(); + } + }); + } else { + new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) + .setCancelable(false) + .setTitle(R.string.app_name) + .setMessage(R.string.no_internet_msg) + .setNegativeButton(R.string.ok, (d, w) -> finish()) + .show(); + } + + } +} diff --git a/app/src/stub/java/com/topjohnwu/magisk/NoUIActivity.java b/app/src/stub/java/com/topjohnwu/magisk/NoUIActivity.java deleted file mode 100644 index 200b0cb27..000000000 --- a/app/src/stub/java/com/topjohnwu/magisk/NoUIActivity.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.topjohnwu.magisk; - -import android.Manifest; -import android.app.AlertDialog; -import android.os.AsyncTask; -import android.os.Bundle; - -import com.topjohnwu.magisk.components.BaseActivity; -import com.topjohnwu.magisk.receivers.ManagerInstall; -import com.topjohnwu.magisk.utils.Download; -import com.topjohnwu.magisk.utils.WebService; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Locale; - -public class NoUIActivity extends BaseActivity { - - private String apkLink; - private String version; - private int versionCode; - - public static final String URL = - "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (Download.checkNetworkStatus(this)) { - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - String str = WebService.getString(URL); - try { - JSONObject json = new JSONObject(str); - JSONObject manager = json.getJSONObject("app"); - version = manager.getString("version"); - versionCode = manager.getInt("versionCode"); - apkLink = manager.getString("link"); - } catch (JSONException e) { - e.printStackTrace(); - finish(); - return; - } - runOnUiThread(() -> { - String filename = String.format(Locale.US, "MagiskManager-v%s(%d).apk", - version, versionCode); - new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) - .setCancelable(false) - .setTitle(R.string.app_name) - .setMessage(R.string.upgrade_msg) - .setPositiveButton(R.string.yes, (d, w) -> runWithPermission(new String[] - { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> { - Download.receive(this, new ManagerInstall(), apkLink, filename); - finish(); - })) - .setNegativeButton(R.string.no_thanks, (d, w) -> finish()) - .show(); - }); - }); - } else { - new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) - .setCancelable(false) - .setTitle(R.string.app_name) - .setMessage(R.string.no_internet_msg) - .setNegativeButton(R.string.ok, (d, w) -> finish()) - .show(); - } - - } -} diff --git a/app/src/stub/java/com/topjohnwu/magisk/components/FlavorActivity.java b/app/src/stub/java/com/topjohnwu/magisk/components/FlavorActivity.java deleted file mode 100644 index eda11a52e..000000000 --- a/app/src/stub/java/com/topjohnwu/magisk/components/FlavorActivity.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.topjohnwu.magisk.components; - -import android.app.Activity; - -public abstract class FlavorActivity extends Activity {}