Retain parasitic manager in recent tasks (#1417)
This commit is contained in:
parent
5656f1b330
commit
b1ae31b151
|
|
@ -74,6 +74,7 @@ import org.lsposed.manager.ui.fragment.AppListFragment;
|
||||||
import org.lsposed.manager.ui.fragment.CompileDialogFragment;
|
import org.lsposed.manager.ui.fragment.CompileDialogFragment;
|
||||||
import org.lsposed.manager.util.GlideApp;
|
import org.lsposed.manager.util.GlideApp;
|
||||||
import org.lsposed.manager.util.ModuleUtil;
|
import org.lsposed.manager.util.ModuleUtil;
|
||||||
|
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -89,7 +90,7 @@ import rikka.core.util.ResourceUtils;
|
||||||
import rikka.widget.switchbar.SwitchBar;
|
import rikka.widget.switchbar.SwitchBar;
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
public class ScopeAdapter extends RecyclerView.Adapter<ScopeAdapter.ViewHolder> implements Filterable {
|
public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder> implements Filterable {
|
||||||
|
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final AppListFragment fragment;
|
private final AppListFragment fragment;
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ public class RepoLoader {
|
||||||
.build()).enqueue(new Callback() {
|
.build()).enqueue(new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
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) {
|
for (Listener listener : listeners) {
|
||||||
listener.onThrowable(e);
|
listener.onThrowable(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,25 @@
|
||||||
|
|
||||||
package org.lsposed.manager.ui.activity.base;
|
package org.lsposed.manager.ui.activity.base;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
|
|
||||||
import com.google.android.material.color.DynamicColors;
|
import com.google.android.material.color.DynamicColors;
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
|
import org.lsposed.manager.App;
|
||||||
import org.lsposed.manager.BuildConfig;
|
import org.lsposed.manager.BuildConfig;
|
||||||
import org.lsposed.manager.ConfigManager;
|
import org.lsposed.manager.ConfigManager;
|
||||||
import org.lsposed.manager.R;
|
import org.lsposed.manager.R;
|
||||||
|
|
@ -69,6 +77,36 @@ public class BaseActivity extends MaterialActivity {
|
||||||
.show();
|
.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
|
@Override
|
||||||
public void onApplyUserThemeResource(@NonNull Resources.Theme theme, boolean isDecorView) {
|
public void onApplyUserThemeResource(@NonNull Resources.Theme theme, boolean isDecorView) {
|
||||||
theme.applyStyle(ThemeUtil.getNightThemeStyleRes(this), true);
|
theme.applyStyle(ThemeUtil.getNightThemeStyleRes(this), true);
|
||||||
|
|
@ -97,6 +135,5 @@ public class BaseActivity extends MaterialActivity {
|
||||||
window.setNavigationBarColor(Color.TRANSPARENT);
|
window.setNavigationBarColor(Color.TRANSPARENT);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,9 @@ import org.lsposed.manager.ConfigManager;
|
||||||
import org.lsposed.manager.R;
|
import org.lsposed.manager.R;
|
||||||
import org.lsposed.manager.databinding.FragmentLogsBinding;
|
import org.lsposed.manager.databinding.FragmentLogsBinding;
|
||||||
import org.lsposed.manager.databinding.ItemLogBinding;
|
import org.lsposed.manager.databinding.ItemLogBinding;
|
||||||
|
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -189,10 +189,9 @@ public class LogsFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reloadLogs() {
|
private void reloadLogs() {
|
||||||
ParcelFileDescriptor parcelFileDescriptor = ConfigManager.getLog(verbose);
|
var parcelFileDescriptor = ConfigManager.getLog(verbose);
|
||||||
if (parcelFileDescriptor != null) {
|
if (parcelFileDescriptor != null)
|
||||||
new LogsReader().execute(parcelFileDescriptor.getFileDescriptor());
|
new LogsReader().execute(parcelFileDescriptor);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear() {
|
private void clear() {
|
||||||
|
|
@ -235,11 +234,10 @@ public class LogsFragment extends BaseFragment {
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private class LogsReader extends AsyncTask<FileDescriptor, Integer, List<String>> {
|
private class LogsReader extends AsyncTask<ParcelFileDescriptor, Integer, List<String>> {
|
||||||
private AlertDialog mProgressDialog;
|
private AlertDialog mProgressDialog;
|
||||||
private final Runnable mRunnable = new Runnable() {
|
private final Runnable mRunnable = () -> {
|
||||||
@Override
|
synchronized (LogsReader.this) {
|
||||||
public void run() {
|
|
||||||
if (!requireActivity().isFinishing()) {
|
if (!requireActivity().isFinishing()) {
|
||||||
mProgressDialog.show();
|
mProgressDialog.show();
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +245,7 @@ public class LogsFragment extends BaseFragment {
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
synchronized protected void onPreExecute() {
|
||||||
mProgressDialog = new MaterialAlertDialogBuilder(requireActivity()).create();
|
mProgressDialog = new MaterialAlertDialogBuilder(requireActivity()).create();
|
||||||
mProgressDialog.setMessage(getString(R.string.loading));
|
mProgressDialog.setMessage(getString(R.string.loading));
|
||||||
mProgressDialog.setCancelable(false);
|
mProgressDialog.setCancelable(false);
|
||||||
|
|
@ -255,12 +253,12 @@ public class LogsFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> doInBackground(FileDescriptor... log) {
|
protected List<String> doInBackground(ParcelFileDescriptor... log) {
|
||||||
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
|
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
|
||||||
|
|
||||||
List<String> logs = new ArrayList<>();
|
List<String> 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;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
logs.add(line);
|
logs.add(line);
|
||||||
|
|
@ -274,7 +272,7 @@ public class LogsFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(List<String> logs) {
|
synchronized protected void onPostExecute(List<String> logs) {
|
||||||
adapter.setLogs(logs);
|
adapter.setLogs(logs);
|
||||||
|
|
||||||
handler.removeCallbacks(mRunnable);
|
handler.removeCallbacks(mRunnable);
|
||||||
|
|
@ -290,7 +288,23 @@ public class LogsFragment extends BaseFragment {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LogsAdapter extends RecyclerView.Adapter<LogsAdapter.ViewHolder> {
|
@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<LogsAdapter.ViewHolder> {
|
||||||
ArrayList<String> logs = new ArrayList<>();
|
ArrayList<String> logs = new ArrayList<>();
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
@ -305,7 +319,7 @@ public class LogsFragment extends BaseFragment {
|
||||||
TextView view = holder.textView;
|
TextView view = holder.textView;
|
||||||
view.setText(logs.get(position));
|
view.setText(logs.get(position));
|
||||||
view.measure(0, 0);
|
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();
|
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
|
||||||
layoutParams.width = desiredWidth;
|
layoutParams.width = desiredWidth;
|
||||||
if (binding.recyclerView.getWidth() < desiredWidth) {
|
if (binding.recyclerView.getWidth() < desiredWidth) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@
|
||||||
package org.lsposed.manager.ui.fragment;
|
package org.lsposed.manager.ui.fragment;
|
||||||
|
|
||||||
import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
|
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.annotation.SuppressLint;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
@ -158,7 +157,6 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
for (var user : users) {
|
for (var user : users) {
|
||||||
var adapter = new ModuleAdapter(user);
|
var adapter = new ModuleAdapter(user);
|
||||||
adapter.setHasStableIds(true);
|
adapter.setHasStableIds(true);
|
||||||
adapter.setStateRestorationPolicy(PREVENT_WHEN_EMPTY);
|
|
||||||
adapters.add(adapter);
|
adapters.add(adapter);
|
||||||
tabTitles.add(user.name);
|
tabTitles.add(user.name);
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +170,6 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi
|
||||||
} else {
|
} else {
|
||||||
var adapter = new ModuleAdapter(null);
|
var adapter = new ModuleAdapter(null);
|
||||||
adapter.setHasStableIds(true);
|
adapter.setHasStableIds(true);
|
||||||
adapter.setStateRestorationPolicy(PREVENT_WHEN_EMPTY);
|
|
||||||
adapters.add(adapter);
|
adapters.add(adapter);
|
||||||
binding.viewPager.setUserInputEnabled(false);
|
binding.viewPager.setUserInputEnabled(false);
|
||||||
binding.tabLayout.setVisibility(View.GONE);
|
binding.tabLayout.setVisibility(View.GONE);
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ import org.lsposed.manager.databinding.ItemOnlinemoduleBinding;
|
||||||
import org.lsposed.manager.repo.RepoLoader;
|
import org.lsposed.manager.repo.RepoLoader;
|
||||||
import org.lsposed.manager.repo.model.OnlineModule;
|
import org.lsposed.manager.repo.model.OnlineModule;
|
||||||
import org.lsposed.manager.util.ModuleUtil;
|
import org.lsposed.manager.util.ModuleUtil;
|
||||||
|
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -178,7 +179,7 @@ public class RepoFragment extends BaseFragment implements RepoLoader.Listener {
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RepoAdapter extends RecyclerView.Adapter<RepoAdapter.ViewHolder> implements Filterable {
|
private class RepoAdapter extends SimpleStatefulAdaptor<RepoAdapter.ViewHolder> implements Filterable {
|
||||||
private List<OnlineModule> fullList, showList;
|
private List<OnlineModule> fullList, showList;
|
||||||
private final LabelComparator labelComparator = new LabelComparator();
|
private final LabelComparator labelComparator = new LabelComparator();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ import org.lsposed.manager.repo.model.Release;
|
||||||
import org.lsposed.manager.repo.model.ReleaseAsset;
|
import org.lsposed.manager.repo.model.ReleaseAsset;
|
||||||
import org.lsposed.manager.ui.widget.LinkifyTextView;
|
import org.lsposed.manager.ui.widget.LinkifyTextView;
|
||||||
import org.lsposed.manager.util.NavUtil;
|
import org.lsposed.manager.util.NavUtil;
|
||||||
|
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||||
import org.lsposed.manager.util.chrome.CustomTabsURLSpan;
|
import org.lsposed.manager.util.chrome.CustomTabsURLSpan;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
@ -234,7 +235,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InformationAdapter extends RecyclerView.Adapter<InformationAdapter.ViewHolder> {
|
private class InformationAdapter extends SimpleStatefulAdaptor<InformationAdapter.ViewHolder> {
|
||||||
private final OnlineModule module;
|
private final OnlineModule module;
|
||||||
|
|
||||||
private int rowCount = 0;
|
private int rowCount = 0;
|
||||||
|
|
@ -320,7 +321,7 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReleaseAdapter extends RecyclerView.Adapter<ReleaseAdapter.ViewHolder> {
|
private class ReleaseAdapter extends SimpleStatefulAdaptor<ReleaseAdapter.ViewHolder> {
|
||||||
private List<Release> items;
|
private List<Release> items;
|
||||||
private final Resources resources = App.getInstance().getResources();
|
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<PagerAdapter.ViewHolder> {
|
private class PagerAdapter extends SimpleStatefulAdaptor<PagerAdapter.ViewHolder> {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -451,11 +452,13 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.Listene
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
|
RecyclerView.Adapter adapter;
|
||||||
if (position == 1) {
|
if (position == 1) {
|
||||||
holder.recyclerView.setAdapter(releaseAdapter = new ReleaseAdapter());
|
adapter = releaseAdapter = new ReleaseAdapter();
|
||||||
} else {
|
} else {
|
||||||
holder.recyclerView.setAdapter(new InformationAdapter(module));
|
adapter = new InformationAdapter(module);
|
||||||
}
|
}
|
||||||
|
holder.recyclerView.setAdapter(adapter);
|
||||||
holder.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
holder.recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||||
RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true);
|
RecyclerViewKt.fixEdgeEffect(holder.recyclerView, false, true);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@ import android.util.DisplayMetrics;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.lsposed.manager.R;
|
import org.lsposed.manager.R;
|
||||||
|
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
||||||
|
|
||||||
import rikka.core.util.ResourceUtils;
|
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 TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||||
private final String emptyText;
|
private final String emptyText;
|
||||||
private final AdapterDataObserver emptyObserver = new AdapterDataObserver() {
|
private final AdapterDataObserver emptyObserver = new AdapterDataObserver() {
|
||||||
|
|
@ -109,8 +109,7 @@ public class EmptyStateRecyclerView extends BorderRecyclerView {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract static class EmptyStateAdapter<T extends ViewHolder> extends Adapter<T> {
|
public abstract static class EmptyStateAdapter<T extends ViewHolder> extends SimpleStatefulAdaptor<T> {
|
||||||
abstract public boolean isLoaded();
|
abstract public boolean isLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ package org.lsposed.manager.ui.widget;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
|
@ -136,4 +138,22 @@ public class ExpandableTextView extends MaterialTextView {
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> implements StatefulAdapter {
|
||||||
|
HashMap<Long, SparseArray<Parcelable>> 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<Object> 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<Parcelable>();
|
||||||
|
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<Parcelable>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/swipeRefreshLayout">
|
android:id="@+id/swipeRefreshLayout">
|
||||||
|
|
||||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
<org.lsposed.manager.ui.widget.StatefulRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
<org.lsposed.manager.ui.widget.StatefulRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:hideAnimationBehavior="outward" />
|
app:hideAnimationBehavior="outward" />
|
||||||
|
|
||||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
|
<org.lsposed.manager.ui.widget.StatefulRecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
~ Copyright (C) 2020 EdXposed Contributors
|
~ Copyright (C) 2020 EdXposed Contributors
|
||||||
~ Copyright (C) 2021 LSPosed Contributors
|
~ Copyright (C) 2021 LSPosed Contributors
|
||||||
-->
|
-->
|
||||||
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.lsposed.manager.ui.widget.StatefulRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ProviderInfo;
|
import android.content.pm.ProviderInfo;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.PersistableBundle;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.AndroidRuntimeException;
|
import android.util.AndroidRuntimeException;
|
||||||
|
|
@ -31,6 +33,8 @@ import java.lang.reflect.Method;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
import de.robv.android.xposed.XC_MethodHook;
|
||||||
import de.robv.android.xposed.XC_MethodReplacement;
|
import de.robv.android.xposed.XC_MethodReplacement;
|
||||||
|
|
@ -43,6 +47,8 @@ public class ParasiticManagerHooker {
|
||||||
|
|
||||||
private static PackageInfo managerPkgInfo = null;
|
private static PackageInfo managerPkgInfo = null;
|
||||||
private static int managerFd = -1;
|
private static int managerFd = -1;
|
||||||
|
private final static Map<String, Bundle> states = new ConcurrentHashMap<>();
|
||||||
|
private final static Map<String, PersistableBundle> persistentStates = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private synchronized static PackageInfo getManagerPkgInfo(ApplicationInfo appInfo) {
|
private synchronized static PackageInfo getManagerPkgInfo(ApplicationInfo appInfo) {
|
||||||
if (managerPkgInfo == null && appInfo != null) {
|
if (managerPkgInfo == null && appInfo != null) {
|
||||||
|
|
@ -111,7 +117,8 @@ public class ParasiticManagerHooker {
|
||||||
protected void beforeHookedMethod(MethodHookParam param) {
|
protected void beforeHookedMethod(MethodHookParam param) {
|
||||||
for (var i = 0; i < param.args.length; ++i) {
|
for (var i = 0; i < param.args.length; ++i) {
|
||||||
if (param.args[i] instanceof ActivityInfo) {
|
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;
|
if (pkgInfo == null) return;
|
||||||
for (var activity : pkgInfo.activities) {
|
for (var activity : pkgInfo.activities) {
|
||||||
if ("org.lsposed.manager.ui.activity.MainActivity".equals(activity.name)) {
|
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());
|
var activityClientRecordClass = XposedHelpers.findClass("android.app.ActivityThread$ActivityClientRecord", ActivityThread.class.getClassLoader());
|
||||||
XposedBridge.hookAllConstructors(activityClientRecordClass, activityHooker);
|
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) {
|
private static void checkIntent(ILSPManagerService managerService, Intent intent) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package android.app;
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.res.CompatibilityInfo;
|
import android.content.res.CompatibilityInfo;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.PersistableBundle;
|
||||||
|
|
||||||
public final class ActivityThread {
|
public final class ActivityThread {
|
||||||
public static ActivityThread currentActivityThread() {
|
public static ActivityThread currentActivityThread() {
|
||||||
|
|
@ -45,6 +47,7 @@ public final class ActivityThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static final class ActivityClientRecord {
|
public static final class ActivityClientRecord {
|
||||||
|
Bundle state;
|
||||||
|
PersistableBundle persistentState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package android.os;
|
||||||
|
|
||||||
|
public class PersistableBundle {
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue