Retain parasitic manager in recent tasks (#1417)

This commit is contained in:
LoveSy 2021-11-19 19:32:17 +08:00 committed by GitHub
parent 5656f1b330
commit b1ae31b151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 290 additions and 37 deletions

View File

@ -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<ScopeAdapter.ViewHolder> implements Filterable {
public class ScopeAdapter extends SimpleStatefulAdaptor<ScopeAdapter.ViewHolder> implements Filterable {
private final Activity activity;
private final AppListFragment fragment;

View File

@ -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);
}

View File

@ -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);
}
});
}
}

View File

@ -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<FileDescriptor, Integer, List<String>> {
private class LogsReader extends AsyncTask<ParcelFileDescriptor, Integer, List<String>> {
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<String> doInBackground(FileDescriptor... log) {
protected List<String> doInBackground(ParcelFileDescriptor... log) {
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
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;
while ((line = reader.readLine()) != null) {
logs.add(line);
@ -274,7 +272,7 @@ public class LogsFragment extends BaseFragment {
}
@Override
protected void onPostExecute(List<String> logs) {
synchronized protected void onPostExecute(List<String> logs) {
adapter.setLogs(logs);
handler.removeCallbacks(mRunnable);
@ -290,7 +288,23 @@ public class LogsFragment extends BaseFragment {
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<>();
@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) {

View File

@ -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);

View File

@ -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<RepoAdapter.ViewHolder> implements Filterable {
private class RepoAdapter extends SimpleStatefulAdaptor<RepoAdapter.ViewHolder> implements Filterable {
private List<OnlineModule> fullList, showList;
private final LabelComparator labelComparator = new LabelComparator();

View File

@ -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<InformationAdapter.ViewHolder> {
private class InformationAdapter extends SimpleStatefulAdaptor<InformationAdapter.ViewHolder> {
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<ReleaseAdapter.ViewHolder> {
private class ReleaseAdapter extends SimpleStatefulAdaptor<ReleaseAdapter.ViewHolder> {
private List<Release> 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<PagerAdapter.ViewHolder> {
private class PagerAdapter extends SimpleStatefulAdaptor<PagerAdapter.ViewHolder> {
@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;

View File

@ -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<T extends ViewHolder> extends Adapter<T> {
public abstract static class EmptyStateAdapter<T extends ViewHolder> extends SimpleStatefulAdaptor<T> {
abstract public boolean isLoaded();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -73,7 +73,7 @@
android:layout_height="match_parent"
android:id="@+id/swipeRefreshLayout">
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
<org.lsposed.manager.ui.widget.StatefulRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -70,7 +70,7 @@
android:scrollbars="none"
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:layout_width="wrap_content"
android:layout_height="match_parent"

View File

@ -61,7 +61,7 @@
android:visibility="gone"
app:hideAnimationBehavior="outward" />
<org.lsposed.manager.ui.widget.EmptyStateRecyclerView
<org.lsposed.manager.ui.widget.StatefulRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -17,7 +17,7 @@
~ Copyright (C) 2020 EdXposed 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"
android:id="@+id/recyclerView"
android:layout_width="match_parent"

View File

@ -14,7 +14,9 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.util.AndroidRuntimeException;
@ -31,6 +33,8 @@ import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
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_MethodReplacement;
@ -43,6 +47,8 @@ public class ParasiticManagerHooker {
private static PackageInfo managerPkgInfo = null;
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) {
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) {

View File

@ -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;
}
}

View File

@ -0,0 +1,4 @@
package android.os;
public class PersistableBundle {
}