From b1ae31b151a204290d44e746231b26781d2378b5 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Fri, 19 Nov 2021 19:32:17 +0800 Subject: [PATCH] Retain parasitic manager in recent tasks (#1417) --- .../manager/adapters/ScopeAdapter.java | 3 +- .../org/lsposed/manager/repo/RepoLoader.java | 2 +- .../ui/activity/base/BaseActivity.java | 39 ++++++++- .../manager/ui/fragment/LogsFragment.java | 44 ++++++---- .../manager/ui/fragment/ModulesFragment.java | 3 - .../manager/ui/fragment/RepoFragment.java | 3 +- .../manager/ui/fragment/RepoItemFragment.java | 13 +-- .../ui/widget/EmptyStateRecyclerView.java | 7 +- .../manager/ui/widget/ExpandableTextView.java | 20 +++++ .../ui/widget/StatefulRecyclerView.java | 52 ++++++++++++ .../manager/util/SimpleStatefulAdaptor.java | 85 +++++++++++++++++++ app/src/main/res/layout/fragment_app_list.xml | 2 +- app/src/main/res/layout/fragment_logs.xml | 2 +- app/src/main/res/layout/fragment_repo.xml | 2 +- .../res/layout/item_repo_recyclerview.xml | 2 +- .../lspd/util/ParasiticManagerHooker.java | 39 ++++++++- .../main/java/android/app/ActivityThread.java | 5 +- .../java/android/os/PersistableBundle.java | 4 + 18 files changed, 290 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/org/lsposed/manager/ui/widget/StatefulRecyclerView.java create mode 100644 app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java create mode 100644 hiddenapi-stubs/src/main/java/android/os/PersistableBundle.java diff --git a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java index ac956ef6..ae545d45 100644 --- a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java +++ b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java @@ -74,6 +74,7 @@ import org.lsposed.manager.ui.fragment.AppListFragment; import org.lsposed.manager.ui.fragment.CompileDialogFragment; import org.lsposed.manager.util.GlideApp; import org.lsposed.manager.util.ModuleUtil; +import org.lsposed.manager.util.SimpleStatefulAdaptor; import java.time.LocalDateTime; import java.util.ArrayList; @@ -89,7 +90,7 @@ import rikka.core.util.ResourceUtils; import rikka.widget.switchbar.SwitchBar; @SuppressLint("NotifyDataSetChanged") -public class ScopeAdapter extends RecyclerView.Adapter implements Filterable { +public class ScopeAdapter extends SimpleStatefulAdaptor implements Filterable { private final Activity activity; private final AppListFragment fragment; diff --git a/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java b/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java index 49ec1b36..f4fbc704 100644 --- a/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java +++ b/app/src/main/java/org/lsposed/manager/repo/RepoLoader.java @@ -98,7 +98,7 @@ public class RepoLoader { .build()).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { - Log.e(App.TAG, call.request().url() + e.getMessage()); + Log.e(App.TAG, call.request().url().toString(), e); for (Listener listener : listeners) { listener.onThrowable(e); } diff --git a/app/src/main/java/org/lsposed/manager/ui/activity/base/BaseActivity.java b/app/src/main/java/org/lsposed/manager/ui/activity/base/BaseActivity.java index 62eb8dfa..f1b94e6c 100644 --- a/app/src/main/java/org/lsposed/manager/ui/activity/base/BaseActivity.java +++ b/app/src/main/java/org/lsposed/manager/ui/activity/base/BaseActivity.java @@ -20,17 +20,25 @@ package org.lsposed.manager.ui.activity.base; +import android.app.ActivityManager; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Rect; import android.os.Bundle; +import android.util.Log; import android.view.Window; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; import com.google.android.material.color.DynamicColors; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import org.lsposed.manager.App; import org.lsposed.manager.BuildConfig; import org.lsposed.manager.ConfigManager; import org.lsposed.manager.R; @@ -69,6 +77,36 @@ public class BaseActivity extends MaterialActivity { .show(); } + @Override + protected void onStart() { + super.onStart(); + for (var task : getSystemService(ActivityManager.class).getAppTasks()) { + task.setExcludeFromRecents(false); + } + Bitmap icon; + try { + var res = getResources().getDrawable(R.mipmap.ic_launcher, getTheme()); + var size = getResources().getDimensionPixelSize( + android.R.dimen.app_icon_size); + var tmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + res.setBounds(new Rect(0, 0, size, size)); + Canvas c = new Canvas(tmp); + res.draw(c); + var drawable = RoundedBitmapDrawableFactory.create(getResources(), tmp); + drawable.setBounds(new Rect(0, 0, size, size)); + icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + c = new Canvas(icon); + drawable.setCircular(true); + drawable.draw(c); + tmp.recycle(); + } catch (Throwable e) { + Log.w(App.TAG, "load icon", e); + icon = BitmapFactory.decodeResource(Resources.getSystem(), android.R.drawable.ic_dialog_info); + } + setTaskDescription(new ActivityManager.TaskDescription(getResources().getString(R.string.app_name), icon)); + icon.recycle(); + } + @Override public void onApplyUserThemeResource(@NonNull Resources.Theme theme, boolean isDecorView) { theme.applyStyle(ThemeUtil.getNightThemeStyleRes(this), true); @@ -97,6 +135,5 @@ public class BaseActivity extends MaterialActivity { window.setNavigationBarColor(Color.TRANSPARENT); } }); - } } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java index 4fb08cbb..221b74b1 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java @@ -56,9 +56,9 @@ import org.lsposed.manager.ConfigManager; import org.lsposed.manager.R; import org.lsposed.manager.databinding.FragmentLogsBinding; import org.lsposed.manager.databinding.ItemLogBinding; +import org.lsposed.manager.util.SimpleStatefulAdaptor; import java.io.BufferedReader; -import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -189,10 +189,9 @@ public class LogsFragment extends BaseFragment { } private void reloadLogs() { - ParcelFileDescriptor parcelFileDescriptor = ConfigManager.getLog(verbose); - if (parcelFileDescriptor != null) { - new LogsReader().execute(parcelFileDescriptor.getFileDescriptor()); - } + var parcelFileDescriptor = ConfigManager.getLog(verbose); + if (parcelFileDescriptor != null) + new LogsReader().execute(parcelFileDescriptor); } private void clear() { @@ -235,11 +234,10 @@ public class LogsFragment extends BaseFragment { @SuppressWarnings("deprecation") @SuppressLint("StaticFieldLeak") - private class LogsReader extends AsyncTask> { + private class LogsReader extends AsyncTask> { private AlertDialog mProgressDialog; - private final Runnable mRunnable = new Runnable() { - @Override - public void run() { + private final Runnable mRunnable = () -> { + synchronized (LogsReader.this) { if (!requireActivity().isFinishing()) { mProgressDialog.show(); } @@ -247,7 +245,7 @@ public class LogsFragment extends BaseFragment { }; @Override - protected void onPreExecute() { + synchronized protected void onPreExecute() { mProgressDialog = new MaterialAlertDialogBuilder(requireActivity()).create(); mProgressDialog.setMessage(getString(R.string.loading)); mProgressDialog.setCancelable(false); @@ -255,12 +253,12 @@ public class LogsFragment extends BaseFragment { } @Override - protected List doInBackground(FileDescriptor... log) { + protected List doInBackground(ParcelFileDescriptor... log) { Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2); List logs = new ArrayList<>(); - try (InputStream inputStream = new FileInputStream(log[0]); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + try (var pfd = log[0]; InputStream inputStream = new FileInputStream(pfd.getFileDescriptor()); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { String line; while ((line = reader.readLine()) != null) { logs.add(line); @@ -274,7 +272,7 @@ public class LogsFragment extends BaseFragment { } @Override - protected void onPostExecute(List logs) { + synchronized protected void onPostExecute(List logs) { adapter.setLogs(logs); handler.removeCallbacks(mRunnable); @@ -290,7 +288,23 @@ public class LogsFragment extends BaseFragment { super.onDestroy(); } - private class LogsAdapter extends RecyclerView.Adapter { + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(LogsFragment.class.getName() + "." + "tab", binding.slidingTabs.getSelectedTabPosition()); + } + + @Override + public void onViewStateRestored(@Nullable Bundle savedInstanceState) { + if (savedInstanceState != null) { + var tabPosition = savedInstanceState.getInt(LogsFragment.class.getName() + "." + "tab", 0); + if (tabPosition < binding.slidingTabs.getTabCount()) + binding.slidingTabs.selectTab(binding.slidingTabs.getTabAt(tabPosition)); + } + super.onViewStateRestored(savedInstanceState); + } + + private class LogsAdapter extends SimpleStatefulAdaptor { ArrayList logs = new ArrayList<>(); @NonNull @@ -305,7 +319,7 @@ public class LogsFragment extends BaseFragment { TextView view = holder.textView; view.setText(logs.get(position)); view.measure(0, 0); - int desiredWidth = (preferences.getBoolean("enable_word_wrap", false)) ? binding.getRoot().getWidth() : view.getMeasuredWidth(); + int desiredWidth = (preferences.getBoolean("enable_word_wrap", false)) ? layoutManager.getWidth() : view.getMeasuredWidth(); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.width = desiredWidth; if (binding.recyclerView.getWidth() < desiredWidth) { diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java index cad65719..ab881c7c 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java @@ -20,7 +20,6 @@ package org.lsposed.manager.ui.fragment; import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; -import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY; import android.annotation.SuppressLint; import android.content.Intent; @@ -158,7 +157,6 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi for (var user : users) { var adapter = new ModuleAdapter(user); adapter.setHasStableIds(true); - adapter.setStateRestorationPolicy(PREVENT_WHEN_EMPTY); adapters.add(adapter); tabTitles.add(user.name); } @@ -172,7 +170,6 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi } else { var adapter = new ModuleAdapter(null); adapter.setHasStableIds(true); - adapter.setStateRestorationPolicy(PREVENT_WHEN_EMPTY); adapters.add(adapter); binding.viewPager.setUserInputEnabled(false); binding.tabLayout.setVisibility(View.GONE); diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java index 139012af..29cfa637 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java @@ -57,6 +57,7 @@ import org.lsposed.manager.databinding.ItemOnlinemoduleBinding; import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.repo.model.OnlineModule; import org.lsposed.manager.util.ModuleUtil; +import org.lsposed.manager.util.SimpleStatefulAdaptor; import java.time.Instant; import java.util.ArrayList; @@ -178,7 +179,7 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener { return super.onOptionsItemSelected(item); } - private class RepoAdapter extends RecyclerView.Adapter implements Filterable { + private class RepoAdapter extends SimpleStatefulAdaptor implements Filterable { private List fullList, showList; private final LabelComparator labelComparator = new LabelComparator(); diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java index a9aea078..c7265dad 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java @@ -65,6 +65,7 @@ import org.lsposed.manager.repo.model.Release; import org.lsposed.manager.repo.model.ReleaseAsset; import org.lsposed.manager.ui.widget.LinkifyTextView; import org.lsposed.manager.util.NavUtil; +import org.lsposed.manager.util.SimpleStatefulAdaptor; import org.lsposed.manager.util.chrome.CustomTabsURLSpan; import java.io.ByteArrayInputStream; @@ -234,7 +235,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene binding = null; } - private class InformationAdapter extends RecyclerView.Adapter { + private class InformationAdapter extends SimpleStatefulAdaptor { private final OnlineModule module; private int rowCount = 0; @@ -320,7 +321,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene } } - private class ReleaseAdapter extends RecyclerView.Adapter { + private class ReleaseAdapter extends SimpleStatefulAdaptor { private List items; private final Resources resources = App.getInstance().getResources(); @@ -430,7 +431,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene } } - private class PagerAdapter extends RecyclerView.Adapter { + private class PagerAdapter extends SimpleStatefulAdaptor { @NonNull @Override @@ -451,11 +452,13 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene break; case 1: case 2: + RecyclerView.Adapter adapter; if (position == 1) { - holder.recyclerView.setAdapter(releaseAdapter = new ReleaseAdapter()); + adapter = releaseAdapter = new ReleaseAdapter(); } else { - holder.recyclerView.setAdapter(new InformationAdapter(module)); + adapter = new InformationAdapter(module); } + holder.recyclerView.setAdapter(adapter); holder.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity())); RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true); break; diff --git a/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java b/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java index 099cf6ad..102010a2 100644 --- a/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java +++ b/app/src/main/java/org/lsposed/manager/ui/widget/EmptyStateRecyclerView.java @@ -31,11 +31,11 @@ import android.util.DisplayMetrics; import androidx.annotation.Nullable; import org.lsposed.manager.R; +import org.lsposed.manager.util.SimpleStatefulAdaptor; import rikka.core.util.ResourceUtils; -import rikka.widget.borderview.BorderRecyclerView; -public class EmptyStateRecyclerView extends BorderRecyclerView { +public class EmptyStateRecyclerView extends StatefulRecyclerView { private final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); private final String emptyText; private final AdapterDataObserver emptyObserver = new AdapterDataObserver() { @@ -109,8 +109,7 @@ public class EmptyStateRecyclerView extends BorderRecyclerView { } - public abstract static class EmptyStateAdapter extends Adapter { + public abstract static class EmptyStateAdapter extends SimpleStatefulAdaptor { abstract public boolean isLoaded(); } - } diff --git a/app/src/main/java/org/lsposed/manager/ui/widget/ExpandableTextView.java b/app/src/main/java/org/lsposed/manager/ui/widget/ExpandableTextView.java index b4ea10cc..e54c1676 100644 --- a/app/src/main/java/org/lsposed/manager/ui/widget/ExpandableTextView.java +++ b/app/src/main/java/org/lsposed/manager/ui/widget/ExpandableTextView.java @@ -22,6 +22,8 @@ package org.lsposed.manager.ui.widget; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Typeface; +import android.os.Bundle; +import android.os.Parcelable; import android.text.Layout; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -136,4 +138,22 @@ public class ExpandableTextView extends MaterialTextView { return false; } + @Override + public Parcelable onSaveInstanceState() { + Bundle bundle = new Bundle(); + bundle.putParcelable("superState", super.onSaveInstanceState()); + bundle.putInt("maxLines", getMaxLines()); + return bundle; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + setMaxLines(bundle.getInt("maxLines")); + state = bundle.getParcelable("superState"); + } + super.onRestoreInstanceState(state); + } + } diff --git a/app/src/main/java/org/lsposed/manager/ui/widget/StatefulRecyclerView.java b/app/src/main/java/org/lsposed/manager/ui/widget/StatefulRecyclerView.java new file mode 100644 index 00000000..c267fdd2 --- /dev/null +++ b/app/src/main/java/org/lsposed/manager/ui/widget/StatefulRecyclerView.java @@ -0,0 +1,52 @@ +package org.lsposed.manager.ui.widget; + +import android.content.Context; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.viewpager2.adapter.StatefulAdapter; + +import rikka.widget.borderview.BorderRecyclerView; + +public class StatefulRecyclerView extends BorderRecyclerView { + public StatefulRecyclerView(@NonNull Context context) { + super(context); + } + + public StatefulRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public StatefulRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + + @Override + public Parcelable onSaveInstanceState() { + Bundle bundle = new Bundle(); + bundle.putParcelable("superState", super.onSaveInstanceState()); + var adapter = getAdapter(); + if (adapter instanceof StatefulAdapter) { + bundle.putParcelable("adaptor", ((StatefulAdapter) adapter).saveState()); + } + return bundle; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + super.onRestoreInstanceState(bundle.getParcelable("superState")); + var adapter = getAdapter(); + if (adapter instanceof StatefulAdapter) { + ((StatefulAdapter) adapter).restoreState(bundle.getParcelable("adaptor")); + } + } else { + super.onRestoreInstanceState(state); + } + } +} diff --git a/app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java b/app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java new file mode 100644 index 00000000..d027c827 --- /dev/null +++ b/app/src/main/java/org/lsposed/manager/util/SimpleStatefulAdaptor.java @@ -0,0 +1,85 @@ +package org.lsposed.manager.util; + +import android.os.Bundle; +import android.os.Parcelable; +import android.util.SparseArray; + +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.adapter.StatefulAdapter; + +import java.util.HashMap; +import java.util.List; + +public abstract class SimpleStatefulAdaptor extends RecyclerView.Adapter implements StatefulAdapter { + HashMap> states = new HashMap<>(); + protected RecyclerView rv = null; + + public SimpleStatefulAdaptor() { + setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY); + } + + @Override + @CallSuper + public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { + rv = recyclerView; + super.onAttachedToRecyclerView(recyclerView); + } + + @Override + public void onViewRecycled(@NonNull T holder) { + saveStateOf(holder); + super.onViewRecycled(holder); + } + + @Override + public final void onBindViewHolder(@NonNull T holder, int position, @NonNull List payloads) { + var state = states.remove(holder.getItemId()); + if (state != null) { + holder.itemView.restoreHierarchyState(state); + } + onBindViewHolder(holder, position); + } + + private void saveStateOf(@NonNull RecyclerView.ViewHolder holder) { + var state = new SparseArray(); + holder.itemView.saveHierarchyState(state); + states.put(holder.getItemId(), state); + } + + @NonNull + public Parcelable saveState() { + for (int childCount = rv.getChildCount(), i = 0; i < childCount; ++i) { + saveStateOf(rv.getChildViewHolder(rv.getChildAt(i))); + } + + var out = new Bundle(); + for (var state : states.entrySet()) { + var item = new Bundle(); + for (int i = 0; i < state.getValue().size(); ++i) { + item.putParcelable(String.valueOf(state.getValue().keyAt(i)), state.getValue().valueAt(i)); + } + out.putParcelable(String.valueOf(state.getKey()), item); + } + return out; + } + + @Override + public void restoreState(@NonNull Parcelable savedState) { + if (savedState instanceof Bundle) { + for (var stateKey : ((Bundle) savedState).keySet()) { + var array = new SparseArray(); + var state = ((Bundle) savedState).getParcelable(stateKey); + if (state instanceof Bundle) { + for (var itemKey : ((Bundle) state).keySet()) { + var item = ((Bundle) state).getParcelable(itemKey); + array.put(Integer.parseInt(itemKey), item); + } + } + states.put(Long.parseLong(stateKey), array); + } + } + } + +} diff --git a/app/src/main/res/layout/fragment_app_list.xml b/app/src/main/res/layout/fragment_app_list.xml index 30788a1c..57451313 100644 --- a/app/src/main/res/layout/fragment_app_list.xml +++ b/app/src/main/res/layout/fragment_app_list.xml @@ -73,7 +73,7 @@ android:layout_height="match_parent" android:id="@+id/swipeRefreshLayout"> - - - - states = new ConcurrentHashMap<>(); + private final static Map persistentStates = new ConcurrentHashMap<>(); private synchronized static PackageInfo getManagerPkgInfo(ApplicationInfo appInfo) { if (managerPkgInfo == null && appInfo != null) { @@ -111,7 +117,8 @@ public class ParasiticManagerHooker { protected void beforeHookedMethod(MethodHookParam param) { for (var i = 0; i < param.args.length; ++i) { if (param.args[i] instanceof ActivityInfo) { - var pkgInfo = getManagerPkgInfo(((ActivityInfo) param.args[i]).applicationInfo); + var aInfo = (ActivityInfo) param.args[i]; + var pkgInfo = getManagerPkgInfo(aInfo.applicationInfo); if (pkgInfo == null) return; for (var activity : pkgInfo.activities) { if ("org.lsposed.manager.ui.activity.MainActivity".equals(activity.name)) { @@ -127,6 +134,20 @@ public class ParasiticManagerHooker { } } } + + @Override + protected void afterHookedMethod(MethodHookParam param) { + var aInfo = (ActivityInfo) XposedHelpers.getObjectField(param.thisObject, "activityInfo"); + Hookers.logD("loading state of " + aInfo.name); + states.computeIfPresent(aInfo.name, (k, v) -> { + XposedHelpers.setObjectField(param.thisObject, "state", v); + return v; + }); + persistentStates.computeIfPresent(aInfo.name, (k, v) -> { + XposedHelpers.setObjectField(param.thisObject, "persistentState", v); + return v; + }); + } }; var activityClientRecordClass = XposedHelpers.findClass("android.app.ActivityThread$ActivityClientRecord", ActivityThread.class.getClassLoader()); XposedBridge.hookAllConstructors(activityClientRecordClass, activityHooker); @@ -221,6 +242,22 @@ public class ParasiticManagerHooker { } } }); + XposedBridge.hookAllMethods(ActivityThread.class, "performStopActivityInner", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + try { + XposedHelpers.callMethod(param.thisObject, Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? "callActivityOnSaveInstanceState" : "callCallActivityOnSaveInstanceState", param.args[0]); + var state = (Bundle) XposedHelpers.getObjectField(param.args[0], "state"); + var persistentState = (PersistableBundle) XposedHelpers.getObjectField(param.args[0], "persistentState"); + var aInfo = (ActivityInfo) XposedHelpers.getObjectField(param.args[0], "activityInfo"); + states.compute(aInfo.name, (k, v) -> state); + persistentStates.compute(aInfo.name, (k, v) -> persistentState); + Hookers.logD("saving state of " + aInfo.name); + } catch (Throwable e) { + Hookers.logE("save state", e); + } + } + }); } private static void checkIntent(ILSPManagerService managerService, Intent intent) { diff --git a/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java b/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java index 829d1e96..9c153c58 100644 --- a/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java +++ b/hiddenapi-stubs/src/main/java/android/app/ActivityThread.java @@ -2,7 +2,9 @@ package android.app; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; +import android.os.Bundle; import android.os.IBinder; +import android.os.PersistableBundle; public final class ActivityThread { public static ActivityThread currentActivityThread() { @@ -45,6 +47,7 @@ public final class ActivityThread { } } public static final class ActivityClientRecord { - + Bundle state; + PersistableBundle persistentState; } } diff --git a/hiddenapi-stubs/src/main/java/android/os/PersistableBundle.java b/hiddenapi-stubs/src/main/java/android/os/PersistableBundle.java new file mode 100644 index 00000000..e06f6f0c --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/os/PersistableBundle.java @@ -0,0 +1,4 @@ +package android.os; + +public class PersistableBundle { +}