Use foreground service for downloading modules

This commit is contained in:
topjohnwu 2018-12-29 17:49:41 +08:00
parent 52137fd64f
commit 646a10d9bf
11 changed files with 160 additions and 31 deletions

View File

@ -6,7 +6,7 @@
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:name="a.q" android:name="a.q"
@ -71,6 +71,8 @@
<!-- Service --> <!-- Service -->
<service android:name="a.j"/>
<!-- Hardcode GMS version --> <!-- Hardcode GMS version -->
<meta-data <meta-data
android:name="com.google.android.gms.version" android:name="com.google.android.gms.version"

View File

@ -1,5 +1,7 @@
package a; package a;
public class j { import com.topjohnwu.magisk.services.DownloadModuleService;
public class j extends DownloadModuleService {
/* stub */ /* stub */
} }

View File

@ -4,6 +4,7 @@ import com.topjohnwu.core.App;
import com.topjohnwu.magisk.components.AboutCardRow; import com.topjohnwu.magisk.components.AboutCardRow;
import com.topjohnwu.magisk.receivers.GeneralReceiver; import com.topjohnwu.magisk.receivers.GeneralReceiver;
import com.topjohnwu.magisk.receivers.ShortcutReceiver; import com.topjohnwu.magisk.receivers.ShortcutReceiver;
import com.topjohnwu.magisk.services.DownloadModuleService;
import com.topjohnwu.magisk.services.UpdateCheckService; import com.topjohnwu.magisk.services.UpdateCheckService;
import java.util.HashMap; import java.util.HashMap;
@ -22,6 +23,7 @@ public class ClassMap {
classMap.put(UpdateCheckService.class, a.g.class); classMap.put(UpdateCheckService.class, a.g.class);
classMap.put(GeneralReceiver.class, a.h.class); classMap.put(GeneralReceiver.class, a.h.class);
classMap.put(ShortcutReceiver.class, a.i.class); classMap.put(ShortcutReceiver.class, a.i.class);
classMap.put(DownloadModuleService.class, a.j.class);
classMap.put(AboutCardRow.class, a.l.class); classMap.put(AboutCardRow.class, a.l.class);
classMap.put(SuRequestActivity.class, a.m.class); classMap.put(SuRequestActivity.class, a.m.class);
} }

View File

@ -1,7 +1,9 @@
package com.topjohnwu.magisk.adapters; package com.topjohnwu.magisk.adapters;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -14,11 +16,12 @@ import android.widget.TextView;
import com.topjohnwu.core.container.Module; import com.topjohnwu.core.container.Module;
import com.topjohnwu.core.container.Repo; import com.topjohnwu.core.container.Repo;
import com.topjohnwu.core.database.RepoDatabaseHelper; import com.topjohnwu.core.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.BaseActivity; import com.topjohnwu.magisk.components.BaseActivity;
import com.topjohnwu.magisk.components.CustomAlertDialog; import com.topjohnwu.magisk.components.CustomAlertDialog;
import com.topjohnwu.magisk.components.MarkDownWindow; import com.topjohnwu.magisk.components.MarkDownWindow;
import com.topjohnwu.magisk.utils.DownloadModule; import com.topjohnwu.magisk.services.DownloadModuleService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -109,14 +112,26 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename())) .setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
.setCancelable(true) .setCancelable(true)
.setPositiveButton(R.string.install, (d, i) -> .setPositiveButton(R.string.install, (d, i) ->
DownloadModule.exec((BaseActivity) context, repo, true)) startDownload((BaseActivity) context, repo, true))
.setNeutralButton(R.string.download, (d, i) -> .setNeutralButton(R.string.download, (d, i) ->
DownloadModule.exec((BaseActivity) context, repo, false)) startDownload((BaseActivity) context, repo, false))
.setNegativeButton(R.string.no_thanks, null) .setNegativeButton(R.string.no_thanks, null)
.show(); .show();
}); });
} }
private void startDownload(BaseActivity activity, Repo repo, Boolean install) {
activity.runWithExternalRW(() -> {
Intent intent = new Intent(activity, ClassMap.get(DownloadModuleService.class))
.putExtra("repo", repo).putExtra("install", install);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(intent);
} else {
activity.startService(intent);
}
});
}
public void notifyDBChanged() { public void notifyDBChanged() {
if (repoCursor != null) if (repoCursor != null)
repoCursor.close(); repoCursor.close();

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.components; package com.topjohnwu.magisk.components;
import android.app.Notification;
import android.widget.Toast; import android.widget.Toast;
import com.topjohnwu.core.App; import com.topjohnwu.core.App;
@ -13,6 +14,7 @@ import androidx.core.app.NotificationManagerCompat;
public class ProgressNotification implements DownloadProgressListener { public class ProgressNotification implements DownloadProgressListener {
private NotificationManagerCompat mgr; private NotificationManagerCompat mgr;
private NotificationCompat.Builder builder; private NotificationCompat.Builder builder;
private Notification notification;
private long prevTime; private long prevTime;
public ProgressNotification(String title) { public ProgressNotification(String title) {
@ -35,12 +37,23 @@ public class ProgressNotification implements DownloadProgressListener {
} }
} }
public NotificationCompat.Builder getNotification() { public NotificationCompat.Builder getNotificationBuilder() {
return builder; return builder;
} }
public Notification getNotification() {
return notification;
}
public void update() { public void update() {
mgr.notify(hashCode(), builder.build()); notification = builder.build();
mgr.notify(hashCode(), notification);
}
private void lastUpdate() {
notification = builder.build();
mgr.cancel(hashCode());
mgr.notify(notification.hashCode(), notification);
} }
public void dlDone() { public void dlDone() {
@ -48,7 +61,7 @@ public class ProgressNotification implements DownloadProgressListener {
.setContentText(App.self.getString(R.string.download_complete)) .setContentText(App.self.getString(R.string.download_complete))
.setSmallIcon(R.drawable.ic_check_circle) .setSmallIcon(R.drawable.ic_check_circle)
.setOngoing(false); .setOngoing(false);
update(); lastUpdate();
} }
public void dlFail() { public void dlFail() {
@ -56,7 +69,7 @@ public class ProgressNotification implements DownloadProgressListener {
.setContentText(App.self.getString(R.string.download_file_error)) .setContentText(App.self.getString(R.string.download_file_error))
.setSmallIcon(R.drawable.ic_cancel) .setSmallIcon(R.drawable.ic_cancel)
.setOngoing(false); .setOngoing(false);
update(); lastUpdate();
} }
public void dismiss() { public void dismiss() {

View File

@ -1,17 +1,20 @@
package com.topjohnwu.magisk.utils; package com.topjohnwu.magisk.services;
import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.IBinder;
import android.widget.Toast;
import com.topjohnwu.core.App;
import com.topjohnwu.core.Const; import com.topjohnwu.core.Const;
import com.topjohnwu.core.container.Repo; import com.topjohnwu.core.container.Repo;
import com.topjohnwu.core.utils.Utils;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.FlashActivity; import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.components.BaseActivity; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.ProgressNotification; import com.topjohnwu.magisk.components.ProgressNotification;
import com.topjohnwu.net.Networking; import com.topjohnwu.net.Networking;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils; import com.topjohnwu.superuser.ShellUtils;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@ -24,16 +27,38 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
public class DownloadModule { import androidx.annotation.Nullable;
public static void exec(BaseActivity activity, Repo repo, boolean install) { public class DownloadModuleService extends Service {
activity.runWithExternalRW(() -> AsyncTask.THREAD_POOL_EXECUTOR.execute(
() -> dlProcessInstall(repo, install))); private boolean running = false;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
} }
private static void dlProcessInstall(Repo repo, boolean install) { @Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (flags == 0 && running) {
Utils.toast(R.string.dl_one_module, Toast.LENGTH_LONG);
} else {
running = true;
Shell.EXECUTOR.execute(() -> {
Repo repo = intent.getParcelableExtra("repo");
boolean install = intent.getBooleanExtra("install", false);
dlProcessInstall(repo, install);
stopSelf();
});
}
return START_REDELIVER_INTENT;
}
private void dlProcessInstall(Repo repo, boolean install) {
File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename()); File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename());
ProgressNotification progress = new ProgressNotification(output.getName()); ProgressNotification progress = new ProgressNotification(output.getName());
startForeground(progress.hashCode(), progress.getNotification());
try { try {
InputStream in = Networking.get(repo.getZipUrl()) InputStream in = Networking.get(repo.getZipUrl())
.setDownloadProgressListener(progress) .setDownloadProgressListener(progress)
@ -41,13 +66,13 @@ public class DownloadModule {
removeTopFolder(in, new BufferedOutputStream(new FileOutputStream(output))); removeTopFolder(in, new BufferedOutputStream(new FileOutputStream(output)));
if (install) { if (install) {
progress.dismiss(); progress.dismiss();
Intent intent = new Intent(App.self, ClassMap.get(FlashActivity.class)); Intent intent = new Intent(this, ClassMap.get(FlashActivity.class));
intent.setData(Uri.fromFile(output)) intent.setData(Uri.fromFile(output))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP); .putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
App.self.startActivity(intent); startActivity(intent);
} else { } else {
progress.getNotification().setContentTitle(output.getName()); progress.getNotificationBuilder().setContentTitle(output.getName());
progress.dlDone(); progress.dlDone();
} }
} catch (Exception e) { } catch (Exception e) {
@ -56,7 +81,7 @@ public class DownloadModule {
} }
} }
private static void removeTopFolder(InputStream in, OutputStream out) throws IOException { private void removeTopFolder(InputStream in, OutputStream out) throws IOException {
try (ZipInputStream zin = new ZipInputStream(in); try (ZipInputStream zin = new ZipInputStream(in);
ZipOutputStream zout = new ZipOutputStream(out)) { ZipOutputStream zout = new ZipOutputStream(out)) {
ZipEntry entry; ZipEntry entry;
@ -73,5 +98,4 @@ public class DownloadModule {
} }
} }
} }
} }

View File

@ -65,7 +65,7 @@ public class DownloadApp {
File patched = apk; File patched = apk;
App app = App.self; App app = App.self;
if (!App.self.getPackageName().equals(BuildConfig.APPLICATION_ID)) { if (!App.self.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
progress.getNotification() progress.getNotificationBuilder()
.setProgress(0, 0, true) .setProgress(0, 0, true)
.setContentTitle(app.getString(R.string.hide_manager_title)) .setContentTitle(app.getString(R.string.hide_manager_title))
.setContentText(""); .setContentText("");
@ -89,7 +89,7 @@ public class DownloadApp {
@Override @Override
public void onDownloadComplete(File apk, ProgressNotification progress) { public void onDownloadComplete(File apk, ProgressNotification progress) {
App app = App.self; App app = App.self;
progress.getNotification() progress.getNotificationBuilder()
.setProgress(0, 0, true) .setProgress(0, 0, true)
.setContentTitle(app.getString(R.string.restore_img_msg)) .setContentTitle(app.getString(R.string.restore_img_msg))
.setContentText(""); .setContentText("");

View File

@ -126,6 +126,7 @@
<string name="setup_msg">Running environment setup…</string> <string name="setup_msg">Running environment setup…</string>
<string name="downloading_toast">Downloading %1$s</string> <string name="downloading_toast">Downloading %1$s</string>
<string name="no_rw_storage">This feature will not work without permission to write external storage.</string> <string name="no_rw_storage">This feature will not work without permission to write external storage.</string>
<string name="dl_one_module">Download one module at a time.</string>
<!--Settings Activity --> <!--Settings Activity -->
<string name="settings_general_category">General</string> <string name="settings_general_category">General</string>

View File

@ -2,12 +2,14 @@ package com.topjohnwu.core.container;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
public abstract class BaseModule implements Comparable<BaseModule> { public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
private String mId, mName, mVersion, mAuthor, mDescription; private String mId, mName, mVersion, mAuthor, mDescription;
private int mVersionCode = -1, minMagiskVersion = -1; private int mVersionCode = -1, minMagiskVersion = -1;
@ -26,6 +28,37 @@ public abstract class BaseModule implements Comparable<BaseModule> {
minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk")); minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk"));
} }
protected BaseModule(Parcel p) {
mId = p.readString();
mName = p.readString();
mVersion = p.readString();
mAuthor = p.readString();
mDescription = p.readString();
mVersionCode = p.readInt();
minMagiskVersion = p.readInt();
}
@Override
public int compareTo(@NonNull BaseModule module) {
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mName);
dest.writeString(mVersion);
dest.writeString(mAuthor);
dest.writeString(mDescription);
dest.writeInt(mVersionCode);
dest.writeInt(minMagiskVersion);
}
private String nonNull(String s) { private String nonNull(String s) {
return s == null ? "" : s; return s == null ? "" : s;
} }
@ -119,9 +152,4 @@ public abstract class BaseModule implements Comparable<BaseModule> {
public int getMinMagiskVersion() { public int getMinMagiskVersion() {
return minMagiskVersion; return minMagiskVersion;
} }
@Override
public int compareTo(@NonNull BaseModule module) {
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
}
} }

View File

@ -1,5 +1,8 @@
package com.topjohnwu.core.container; package com.topjohnwu.core.container;
import android.os.Parcel;
import android.os.Parcelable;
import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile; import com.topjohnwu.superuser.io.SuFile;
@ -32,6 +35,19 @@ public class Module extends BaseModule {
mUpdated = mUpdateFile.exists(); mUpdated = mUpdateFile.exists();
} }
public static final Parcelable.Creator<Module> CREATOR = new Creator<Module>() {
/* It won't be used at any place */
@Override
public Module createFromParcel(Parcel source) {
return null;
}
@Override
public Module[] newArray(int size) {
return null;
}
};
public void createDisableFile() { public void createDisableFile() {
mEnable = !mDisableFile.createNewFile(); mEnable = !mDisableFile.createNewFile();
} }

View File

@ -2,6 +2,8 @@ package com.topjohnwu.core.container;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import com.topjohnwu.core.Const; import com.topjohnwu.core.Const;
import com.topjohnwu.core.utils.Logger; import com.topjohnwu.core.utils.Logger;
@ -23,6 +25,30 @@ public class Repo extends BaseModule {
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update"))); mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
} }
public Repo(Parcel p) {
super(p);
mLastUpdate = new Date(p.readLong());
}
public static final Parcelable.Creator<Repo> CREATOR = new Parcelable.Creator<Repo>() {
@Override
public Repo createFromParcel(Parcel source) {
return new Repo(source);
}
@Override
public Repo[] newArray(int size) {
return new Repo[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(mLastUpdate.getTime());
}
public void update() throws IllegalRepoException { public void update() throws IllegalRepoException {
String props[] = Utils.dlString(getPropUrl()).split("\\n"); String props[] = Utils.dlString(getPropUrl()).split("\\n");
try { try {