Input:
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SwitchCompat;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import cn.wq.myandroidtoolspro.R;
import cn.wq.myandroidtoolspro.helper.DBHelper;
import cn.wq.myandroidtoolspro.helper.IfwUtil;
import cn.wq.myandroidtoolspro.helper.Utils;
import cn.wq.myandroidtoolspro.model.ComponentEntry;
import cn.wq.myandroidtoolspro.model.ReceiverEntry;
import cn.wq.myandroidtoolspro.recyclerview.adapter.AbstractComponentAdapter;
import cn.wq.myandroidtoolspro.recyclerview.multi.MultiSelectableViewHolder;
import cn.wq.myandroidtoolspro.recyclerview.multi.MultiSelectionUtils;
import cn.wq.myandroidtoolspro.recyclerview.toolbar.MultiSectionWithToolbarRecyclerFragment;

Context:
app/src/main/java/cn/wq/myandroidtoolspro/recyclerview/fragment/ReceiverRecyclerListFragment.java
                    if(entry.className==null){
                        continue;
                    }

                    if(!entry.className.contains(".")){
                        entry.className=entry.packageName+"."+entry.className;
                    }else if(entry.className.startsWith(".")){
                        entry.className=entry.packageName+entry.className;
                    }

                    entry.enabledInManifest = parser.getAttributeBooleanValue(ANDROID_NAMESPACE, "enabled",true);
//                    entry.enabled=pm.getComponentEnabledSetting(new ComponentName(
//                            entry.packageName, entry.className)) <= 1;
                    if (isIfw) {
                        entry.isIfwed = IfwUtil.isComponentInIfw(packageName, entry.className, IfwUtil.COMPONENT_FLAG_RECEIVER, mIfwEntry);
                    } else {
                        entry.enabled = Utils.isComponentEnabled(entry, pm);
                    }

                    entry.actions=parseActions(parser);
                    result.add(entry);
                }
            }

            Collections.sort(result, new Comparator<ReceiverEntry>() {
                @Override
                public int compare(ReceiverEntry lhs, ReceiverEntry rhs) {
                    String l = lhs.className.substring(lhs.className
                            .lastIndexOf(".") + 1);
                    String r = rhs.className.substring(rhs.className
                            .lastIndexOf(".") + 1);
                    return l.compareTo(r);
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    private String parseActions(XmlPullParser parser) throws XmlPullParserException, IOException{
        StringBuilder builder=new StringBuilder();
        final int innerDepth=parser.getDepth();
        int type;
        while((type=parser.next())!=XmlPullParser.END_DOCUMENT &&
                (type!=XmlPullParser.END_TAG||parser.getDepth()>innerDepth)){
            if(type==XmlPullParser.END_TAG||type==XmlPullParser.TEXT){
                continue;
            }
            if("action".equals(parser.getName())){
                builder.append(parser.getAttributeValue(ANDROID_NAMESPACE, "name"));
                builder.append("\n");
            }
        }

        if (builder.length() > 0) {
            builder.deleteCharAt(builder.length() - 1);
        }
        return builder.toString();
    }

    private class ReceiverAdapter extends AbstractComponentAdapter<ReceiverEntry> {
        public ReceiverAdapter(Context context) {
            super(context);
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ReceiverViewHolder(LayoutInflater.from(mContext)
                    .inflate(R.layout.item_receiver_list, parent, false));
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            ReceiverViewHolder vHolder= (ReceiverViewHolder) holder;
            ReceiverEntry entry = getItem(position);

            if (getIsFullName()) {
                vHolder.name.setText(entry.className);
            } else {
                vHolder.name.setText(entry.className.substring(entry.className
                        .lastIndexOf(".") + 1));
            }

            vHolder.checkBox.setChecked(entry.enabled);

            if (TextUtils.isEmpty(entry.actions)) {
                vHolder.actions.setVisibility(View.GONE);
            } else {
                vHolder.actions.setVisibility(View.VISIBLE);
                vHolder.actions.setText(entry.actions);
            }
            vHolder.setSelected(getMultiController().isSelectedAtPosition(position));

            if (Utils.isPmByIfw(mContext)) {
                vHolder.checkBox.setVisibility(View.GONE);
                vHolder.wall.setVisibility(entry.isIfwed ? View.VISIBLE : View.INVISIBLE);
            } else {
                vHolder.checkBox.setVisibility(View.VISIBLE);
                vHolder.wall.setVisibility(View.GONE);
                vHolder.name.setTextColor(entry.enabled ? primaryTextColor : redTextColor);
            }
        }

        private  class ReceiverViewHolder extends MultiSelectableViewHolder{
            TextView actions;
            TextView name;
            SwitchCompat checkBox;
            ImageView wall;
            ReceiverViewHolder(View itemView) {
                super(itemView);
                actions= (TextView) itemView.findViewById(R.id.actions);
                name = (TextView) itemView.findViewById(R.id.name);
                checkBox=(SwitchCompat)itemView.findViewById(R.id.checkbox);
                wall = itemView.findViewById(R.id.wall);
            }

            @Override

app/src/main/java/cn/wq/myandroidtoolspro/recyclerview/toolbar/MultiSectionWithToolbarRecyclerFragment.java
public abstract class MultiSectionWithToolbarRecyclerFragment extends MultiSelectionRecyclerListFragment {
    private ActionBar mActionBar;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.recycler_with_toolbar_list_fragment,container,false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Bundle args=getArguments();
        if (args != null && args.getBoolean("ignoreToolbar")) {
            return;
        }

        ViewStub viewStub= (ViewStub) view.findViewById(R.id.view_stub);
        viewStub.inflate();

        Toolbar toolbar=(Toolbar) view.findViewById(R.id.toolbar);
        if (toolbar == null) {
            return;
        }
        AppCompatActivity activity = (AppCompatActivity) getActivity();
        activity.setSupportActionBar(toolbar);
        mActionBar=activity.getSupportActionBar();
        if (mActionBar != null) {
            mActionBar.setDisplayHomeAsUpEnabled(true);
        }

        //是子fragment时让父fragment设置title就行了
        if (args != null && !args.getBoolean("part")) {
            initActionbar(1,args.getString("title"));
        }
    }

    /**
     * @see BaseFragment#initActionbar(int, String)
     */
    protected void initActionbar(int iconType,String title){
        if (mActionBar == null) {
            return;
        }
        if(title==null){
            mActionBar.setTitle(R.string.app_name);
        }else{
            mActionBar.setTitle(title);
        }

        switch (iconType) {
            case 1:
                mActionBar.setHomeAsUpIndicator(R.drawable.ic_arrow_back_white_24dp);
                break;
            case 2:
                mActionBar.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
                break;
            default:
                mActionBar.setHomeAsUpIndicator(R.drawable.ic_menu_white);
                break;
        }
    }

    protected void setToolbarLogo(String packageName) {
        if (mActionBar == null || TextUtils.isEmpty(packageName)) {
            return;
        }
        try {
            int length = (int) (getResources().getDisplayMetrics().density * 24);
            Drawable icon=getContext().getPackageManager().getApplicationIcon(packageName);
            View view=getView();
            if (view != null) {
                Toolbar toolbar= (Toolbar) view.findViewById(R.id.toolbar);
                toolbar.setLogo(icon);
                for (int i = 0; i < toolbar.getChildCount(); i++) {
                    View child = toolbar.getChildAt(i);
                    if (child != null && child instanceof ImageView){
                        ImageView iv = (ImageView) child;
                        if ( iv.getDrawable() == icon ) {
                            ViewGroup.LayoutParams params=iv.getLayoutParams();
                            params.width=params.height=length;
                        }
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        closeActionMode();  //actionMode时在drawer里面调到其它页面不会消失
    }
}

app/src/main/java/cn/wq/myandroidtoolspro/recyclerview/multi/MultiSelectableViewHolder.java
public abstract class MultiSelectableViewHolder
        extends RecyclerView.ViewHolder
        implements View.OnLongClickListener, View.OnClickListener{
    private boolean mIsSelected;
    private Drawable mDefaultBackground,mSelectedBackground;

    public MultiSelectableViewHolder(View itemView) {
        super(itemView);

        if(!itemView.isClickable()){
            itemView.setClickable(true);
        }
        if (!itemView.isLongClickable()) {
            itemView.setLongClickable(true);
        }
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);

        mDefaultBackground = itemView.getBackground();
        setSelectionnModeDrawable(R.color.actionbar_color_light);
    }

    public abstract MultiSelectionUtils.Controller loadMultiController();

    public void setSelectionnModeDrawable(@ColorRes int colorRes) {
        //导致默认的RippleDrawable无效
//        StateListDrawable stateListDrawable = new StateListDrawable();
//        stateListDrawable.addState(StateSet.WILD_CARD,null);
//
//        ColorDrawable drawable = new ColorDrawable(ContextCompat.getColor(itemView.getContext(),colorRes));
//        stateListDrawable.addState(new int[]{android.R.attr.state_selected},drawable);
//        itemView.setBackgroundDrawable(stateListDrawable);

        mSelectedBackground=new ColorDrawable(ContextCompat.getColor(itemView.getContext(),colorRes));
    }

    public void setSelected(boolean selected){
        if (mIsSelected != selected) {
            mIsSelected=selected;
//            itemView.setSelected(selected);
            itemView.setBackgroundDrawable(selected?mSelectedBackground:mDefaultBackground);
        }
    }

    public boolean isSelected() {
        return mIsSelected;
    }

    @Override
    public boolean onLongClick(View view) {
        if (!loadMultiController().isInActionMode()) {
            loadMultiController().startActionMode();
            setSelected(true);
            loadMultiController().onStateChanged(getLayoutPosition(),true);
        }else{
            onClick(view);
        }

        return true;
    }

    @Override
    public void onClick(View view) {
        if (loadMultiController().isInActionMode()) {
            boolean isSelected=isSelected();
            setSelected(!isSelected);
            loadMultiController().onStateChanged(getLayoutPosition(),!isSelected);
            return;
        }
        loadMultiController().onItemClick(getLayoutPosition(),view);
    }

}

app/src/main/java/cn/wq/myandroidtoolspro/helper/Utils.java
public class Utils {
    private static final String TAG = "Utils";
    //	public final static String ACTION_RECEIVER_CHANGED="cn.wq.myandroidtoolspro.receiver_changed";
//	private final static String EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
//	public final static String tempDBPath=System.getenv(EXTERNAL_STORAGE)
//			+ "/temp2.db";
//	public final static String tempSPfrefsPath=System.getenv(EXTERNAL_STORAGE)
//			+ "/temp2.xml";
//	public final static String BACKUP_FILE_DIR=Environment.getExternalStorageDirectory()+"/myandroidtoolspro_backup.txt";
    public final static String ACTION_SORT_CHANGE = "cn.wq.myandroidtoolspro.action_sort_change";
    //	public final static String ACTION_APP_CHANGE="cn.wq.myandroidtoolspro.action_app_change";
    public final static String ACTION_APP_SORT = "cn.wq.myandroidtoolspro.action_app_sort";
    public final static String ACTION_RECEIVER_FINISH = "cn.wq.myandroidtoolspro.action_receiver_finish";
    private final static String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";

    public final static String ACTION_ACTIVITY_FULL_NAME = "ACTION_ACTIVITY_FULL_NAME";
    public final static String ACTION_ACTIVITY_MOVE = "ACTION_ACTIVITY_MOVE";
    public final static String ACTION_ACTIVITY_TEXTSIZE = "ACTION_ACTIVITY_TEXTSIZE";

    private final static String SERVICE_START_MARK = "  * ServiceRecord{";
    //    private final static String SERVICE_START_MARK_PACKAGE = "    packageName=";
//    private final static String SERVICE_START_MARK_PROCESS = "    processName=";
    private final static String SERVICE_START_MARK_APP = "    app=";
    private final static Pattern SERVICE_RECORD_PATTERN = Pattern.compile("[\\w]+ [\\w]+ ([\\w.]+)/([\\w.]+)\\}");
    private final static Pattern SERVICE_APP_PATTERN = Pattern.compile(" +app=ProcessRecord\\{[\\w]+ ([\\d]+):[\\S]+");
    //adb 中 grep 只能用-E 支持多个搜索条件，而且不用转义;不能直接用|分隔
    //"dumpsys activity services | grep -E \"ServiceRecord{|processName=\"";
    private final static String COMMAND_GET_RUNNING_SERVICE_FOR_ALL = "dumpsys activity s | grep -E \"ServiceRecord{|app=\"";
    private final static String COMMAND_GET_RUNNING_SERVICE_FOR_SINGLE = "dumpsys activity s %s| grep -E \"ServiceRecord{|app=\"";

    public static String trimFor160(String name) {
        return name.replaceAll(String.valueOf((char) 160), "");
    }

    public static String getTempDBPath(Context context) {
        return context.getExternalCacheDir() + "/temp2.db";
    }

    public static String getTempDBWalPath(Context context) {
        return context.getExternalCacheDir() + "/temp2.db-wal";
    }

    public static String getTempSPfrefsPath(Context context) {
        return context.getExternalCacheDir() + "/temp2.xml";
    }

    /**
     * 从 attr 中获取颜色
     */
    public static @ColorInt int getColorFromAttr(Context context, @AttrRes int attr) {
        TypedValue value = new TypedValue();
        if(context.getTheme().resolveAttribute(attr, value, true)){
//            return value.data;    //不是Color是ColorStateList
            if (value.resourceId != 0) {
                return ContextCompat.getColor(context, value.resourceId);
            }
        }
        return ContextCompat.getColor(context, R.color.text_primary_black);
    }

//    @ColorRes
//    public static int getPrimaryTextColorRes(Context context) {
//        TypedValue typedValue = new TypedValue();
//        context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
//        return typedValue.resourceId;
//    }

//    private static Picasso mPicasso;
//
//    public static synchronized Picasso getPicassoInstance(Context context) {
//        if (mPicasso == null) {
//            final Context appContext = context.getApplicationContext();
//            mPicasso = new Picasso.Builder(appContext).addRequestHandler(new RequestHandler() {
//                @Override
//                public boolean canHandleRequest(Request data) {
//                    return "icon".equals(data.uri.getScheme());
//                }
//
//                @Override
//                public Result load(Request request, int networkPolicy){
//                    Drawable drawable = null;
//                    try {
//                        drawable = appContext.getPackageManager().getApplicationIcon(request.uri.toString().replace("icon:", ""));
//                    } catch (PackageManager.NameNotFoundException e) {
//                        e.printStackTrace();
//                    }
//
//                    if (drawable instanceof BitmapDrawable) {
//                        return new Result(((BitmapDrawable) drawable).getBitmap(), Picasso.LoadedFrom.DISK);
//                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && drawable instanceof AdaptiveIconDrawable) {
//                        //https://stackoverflow.com/a/46448831/1263423
//                        //android 0 adaptive launch icon
//                        AdaptiveIconDrawable aiDrawable = (AdaptiveIconDrawable) drawable;
//                        Drawable backgroudDr = aiDrawable.getBackground();
//                        Drawable foregroundDr = aiDrawable.getForeground();
//                        Drawable[] drs = new Drawable[]{backgroudDr, foregroundDr};
//
//                        LayerDrawable layerDrawable = new LayerDrawable(drs);
//                        Bitmap bitmap = Bitmap.createBitmap(layerDrawable.getIntrinsicWidth(),
//                                layerDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
//                        Canvas canvas = new Canvas(bitmap);
//                        layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
//                        layerDrawable.draw(canvas);
//
//                        return new Result(bitmap, Picasso.LoadedFrom.DISK);
//                    }
//
//                    //default
//                    drawable = appContext.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
//                    return new Result(((BitmapDrawable) drawable).getBitmap(), Picasso.LoadedFrom.DISK);
//                }
//            }).build();
//        }
//        return mPicasso;
//    }

    public static String getAppLabel(@NonNull PackageManager pm, @NonNull ApplicationInfo aInfo) {
        try {
            CharSequence label = aInfo.loadLabel(pm);
            if (label != null) {
                return Utils.trimFor160(label.toString());
            }
        } catch (Exception e) {
            Utils.debug(TAG, "getAppLabel error," + aInfo.packageName + "," + e.toString());
        }

        return aInfo.packageName;
    }

    public static void loadApkIcon(Fragment fragment, String packageName, ImageView imageView) {
        Glide.with(fragment).load(ApkIconFetcher.PREFIX + packageName)
                .placeholder(android.R.drawable.sym_def_app_icon)
                .error(android.R.drawable.sym_def_app_icon).into(imageView);
    }
    public static void loadApkIcon(Context context, String packageName, ImageView imageView) {
        Glide.with(context).load(ApkIconFetcher.PREFIX + packageName)
                .placeholder(android.R.drawable.sym_def_app_icon)
                .error(android.R.drawable.sym_def_app_icon).into(imageView);
    }

    public static String getUninstallBackupDir() {
        File dir = new File(Environment.getExternalStorageDirectory() + File.separator + "MyAndroidTools"+File.separator+"uninstalled");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir.getAbsolutePath();
    }

    public static String getUninstallBackupDirInShell() {
        File dir = new File(File.separator + "sdcard" + File.separator + "MyAndroidTools"+File.separator+"uninstalled");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir.getAbsolutePath();
    }

    public static List<AppEntry> getAppsWithType(Context context, boolean isSystemApp,
                                                 int type) {
        switch (type) {
            case 0:
                return getAppsAsService(context, isSystemApp);
            case 1:
                return getAppsAsReceiver(context, isSystemApp);
            case 2:
                return getAppsAsActivity(context, isSystemApp);
            case 3:
                return getAppsAsProvider(context, isSystemApp);
            default:
                return null;
        }
    }

    public static List<AppEntry> getAppsAsService(Context context, boolean isSystemApp) {
        List<RunningServiceInfo> runningServiceInfos = getRunningServiceInfos(context, null);

        PackageManager pm = context.getPackageManager();
        List<ApplicationInfo> aInfos = pm.getInstalledApplications(0);
        List<AppEntry> result = new ArrayList<>();

        for (ApplicationInfo aInfo : aInfos) {
            if (aInfo == null) {
                continue;
            }
            if (isSystemApp ? (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    : (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                List<ComponentModel> models = Utils.getComponentModels(context, aInfo.packageName, 0);
                if (models.size() == 0)
                    continue;

                AppEntry entry = new AppEntry();
                entry.totalNum = models.size();
                entry.label = getAppLabel(pm, aInfo);
                if (!Utils.isPmByIfw(context)) {
                    entry.disabledNum = Utils.getComponentDisabledNum(models, pm);
                }
                entry.runningNum = Utils.getRunningNum(aInfo.packageName,
                        runningServiceInfos);
                entry.packageName = aInfo.packageName;
                result.add(entry);
            }
        }

        aInfos.clear();

        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        final int sortType = sp.getInt("sort_service", 0);
        Locale locale = context.getResources().getConfiguration().locale;

        final Collator collator = Collator.getInstance(locale);
        Collections.sort(result, new Comparator<AppEntry>() {
            @Override
            public int compare(AppEntry left, AppEntry right) {
                if (sortType == 1
                        && left.runningNum != right.runningNum) {
                    return right.runningNum - left.runningNum;
                } else if (sortType == 2
                        && left.disabledNum != right.disabledNum) {
                    return right.disabledNum - left.disabledNum;
                }

                return collator.compare(left.label, right.label);
            }
        });

        return result;
    }

    private static List<AppEntry> getAppsAsReceiver(Context context, boolean isSystemApp) {
        PackageManager pm = context.getPackageManager();
        List<ApplicationInfo> aInfos = pm.getInstalledApplications(0);
        List<AppEntry> result = new ArrayList<>();

        SQLiteDatabase db = DBHelper.getInstance(context).getWritableDatabase();
        db.beginTransaction();
        for (ApplicationInfo aInfo : aInfos) {
            if (aInfo == null) {
                continue;
            }
            if (isSystemApp ? (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    : (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                List<ComponentModel> models = Utils.getComponentModels(context, aInfo.packageName, 1);
                if (models.size() == 0)
                    continue;

                AppEntry entry = new AppEntry();
                entry.totalNum = models.size();
                entry.label = getAppLabel(pm, aInfo);
                if (!Utils.isPmByIfw(context)) {
                    entry.disabledNum = Utils.getComponentDisabledNum(models, pm);
                }
                entry.packageName = aInfo.packageName;
                result.add(entry);

                ContentValues values = new ContentValues();
                values.put("app_name", entry.label);
                values.put("package_name", entry.packageName);
                values.put("is_system", isSystemApp ? 1 : 0);
                db.insert(DBHelper.APPS_TABLE_NAME, null, values);
            }
        }
        db.setTransactionSuccessful();
        db.endTransaction();

        aInfos.clear();

        Locale locale = context.getResources().getConfiguration().locale;

        final Collator collator = Collator.getInstance(locale);
        Collections.sort(result, new Comparator<AppEntry>() {
            @Override
            public int compare(AppEntry left, AppEntry right) {
                String lString = left.label;
                String rString = right.label;
                return collator.compare(lString, rString);
            }
        });

        return result;
    }

    private static List<AppEntry> getAppsAsActivity(Context context, boolean isSystemApp) {
        PackageManager pm = context.getPackageManager();
        List<ApplicationInfo> aInfos = pm.getInstalledApplications(0);
        List<AppEntry> result = new ArrayList<>();

        for (ApplicationInfo aInfo : aInfos) {
            if (aInfo == null) {
                continue;
            }
            if (isSystemApp ? (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    : (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                List<ComponentModel> models = Utils.getComponentModels(context, aInfo.packageName, 2);
                if (models.size() == 0)
                    continue;

                AppEntry entry = new AppEntry();
                entry.totalNum = models.size();
                entry.label = getAppLabel(pm, aInfo);
                if (!Utils.isPmByIfw(context)) {
                    entry.disabledNum = Utils.getComponentDisabledNum(models, pm);
                }
                entry.packageName = aInfo.packageName;
                result.add(entry);
            }
        }

        aInfos.clear();

        Locale locale = context.getResources().getConfiguration().locale;

        final Collator collator = Collator.getInstance(locale);
        Collections.sort(result, new Comparator<AppEntry>() {
            @Override
            public int compare(AppEntry left, AppEntry right) {
                String lString = left.label;
                String rString = right.label;
                return collator.compare(lString, rString);
            }
        });

        return result;
    }

    public static List<AppEntry> getAppsAsProvider(Context context, boolean isSystemApp) {
        PackageManager pm = context.getPackageManager();
        List<ApplicationInfo> aInfos = pm.getInstalledApplications(0);
        List<AppEntry> result = new ArrayList<>();

        for (ApplicationInfo aInfo : aInfos) {
            //用了某些xposed框架可能导致 未知错误：Attempt to read from field 'java.lang.String android.content.pm.PackageItemInfo.packageName' on a null object reference
            //https://play.google.com/apps/publish/?dev_acc=04519573207763184232#ErrorClusterDetailsPlace:p=cn.wq.myandroidtools&et=CRASH&lr=LAST_7_DAYS&ecn=java.lang.NullPointerException&tf=unknown&tc=cn.wq.myandroidtools.b.b&tm=a&nid&an&c&s=new_status_desc
            if (aInfo == null) {
                continue;
            }
            if (isSystemApp ? (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    : (aInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                List<ComponentModel> models = Utils.getComponentModels(context, aInfo.packageName, 3);
                if (models.size() == 0)
                    continue;
                AppEntry entry = new AppEntry();
                entry.totalNum = models.size();
                entry.label = getAppLabel(pm, aInfo);
                entry.disabledNum = Utils.getComponentDisabledNum(models, pm);
                entry.packageName = aInfo.packageName;
                result.add(entry);
            }
        }

        aInfos.clear();

        Locale locale = context.getResources().getConfiguration().locale;

        final Collator collator = Collator.getInstance(locale);
        Collections.sort(result, new Comparator<AppEntry>() {
            @Override
            public int compare(AppEntry left, AppEntry right) {
                String lString = left.label;
                String rString = right.label;
                return collator.compare(lString, rString);
            }
        });

        return result;
    }

    public static int getComponentDisabledNum(List<ComponentModel> cModels, PackageManager pm) {
        int num = 0;
        for (ComponentModel component : cModels) {
            if (component == null) {
                continue;
            }

            if ((!isComponentEnabled(component, pm))) {
                num++;
            }
        }
        return num;
    }

    public static int getDisabledNum(int type, String packageName, Context context) {
        return getComponentDisabledNum(getComponentModels(context, packageName, type), context.getPackageManager());
    }

//    public static List<RunningServiceInfo> getRunningServiceInfos(
//            Context context) {
//        return getRunningServiceInfos(context, null);
//    }

    public static boolean isPmByShizuku(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getInt(BaseActivity.PREFERENCE_PM_CHANNER, BaseActivity.PM_CHANNEL_ROOT_COMMAND) == BaseActivity.PM_CHANNEL_SHIZUKU;
    }

    public static boolean isPmByIfw(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getInt(BaseActivity.PREFERENCE_PM_CHANNER, BaseActivity.PM_CHANNEL_ROOT_COMMAND) == BaseActivity.PM_CHANNEL_IFW;
    }

    /**
     * 只获取制定包名的running service；针对Android O开始只能用root命令的情况可以限定包名
     * @param currentPkgName 需要获取正在运行服务的包名，可以为空
     */
    public static List<RunningServiceInfo> getRunningServiceInfos(
            Context context,@Nullable String currentPkgName) {
        final int maxNum;
//            if (!TextUtils.isEmpty(currentPkgName)) {
//                maxNum = 50;    //限制包名时不需要那么多
//            }else{
        maxNum = 500;
//            }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            ActivityManager aManager = (ActivityManager) context
                    .getSystemService(Context.ACTIVITY_SERVICE);

            return aManager.getRunningServices(maxNum);
        } else {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                if (isPmByShizuku(context) && ShizukuClient.getState().isAuthorized()) {
                    try {
                        return ShizukuActivityManagerV26.getServices(maxNum, 0);
                    } catch (Exception e) {
                        com.tencent.mars.xlog.Log.e(TAG, "getRunningServiceInfos by shizuku error", e);
                        Utils.err(TAG, "getRunningServiceInfos by shizuku error", e);
                    }
                }
            }

            //Android O（Android 8）开始变了,必须root才行
            //adb 中 grep 只能用-E 支持多个搜索条件，而且不用转义;不能直接用|分隔
            String cmd;
            if (TextUtils.isEmpty(currentPkgName)) {
                cmd = COMMAND_GET_RUNNING_SERVICE_FOR_ALL;
            }else{
                cmd = String.format(COMMAND_GET_RUNNING_SERVICE_FOR_SINGLE, currentPkgName);
            }
            List<String> result = runRootCommandForResult(cmd);

            List<RunningServiceInfo> rInfoList = new ArrayList<>();
            if (result != null) {
                RunningServiceInfo rInfo = null;
                for (String s : result) {
                    if (TextUtils.isEmpty(s)) {
                        continue;
                    }
                    if (s.startsWith(SERVICE_START_MARK)) {
                        if (rInfo != null) {
                            rInfoList.add(rInfo);
                        }
                        rInfo = new RunningServiceInfo();
                        Matcher matcher = SERVICE_RECORD_PATTERN.matcher(s.substring(SERVICE_START_MARK.length()));
                        if (matcher.matches()) {
                            String packageName = matcher.group(1);
                            String serviceName = matcher.group(2);
                            if (serviceName != null && serviceName.startsWith(".")) {
                                serviceName = packageName + serviceName;
                            }
                            if (serviceName != null) {
                                rInfo.service = new ComponentName(packageName, serviceName);
                            }
                        }
//                    } else if (s.startsWith(START_MARK_PROCESS)) {  //其实不需要processName
//                        if (rInfo != null) {
//                            rInfo.process = s.substring(START_MARK_PROCESS.length());
//                            rInfoList.add(rInfo);
//                            rInfo = null;
//                        }
                    } else if (s.startsWith(SERVICE_START_MARK_APP)) {
                        if (rInfo != null) {
                            Matcher matcher = SERVICE_APP_PATTERN.matcher(s);
                            if (matcher.matches()) {
                                String pid = matcher.group(1);
                                try {
                                    rInfo.pid = Integer.parseInt(pid);
                                } catch (Exception e) {
                                    rInfo.pid = 0;
                                }
                                rInfoList.add(rInfo);
                                rInfo = null;
                            }
                        }
                    }
                }
            }

            //返回未空的话，保存返回数据到文件
//            if (rInfoList.size() == 0) {
//                Utils.debug("-------mat test begin-------");
//                if (result != null && result.size() > 0) {
//                    Utils.debug("running_result 1 size:" + result.size());
//                    String path = context.getExternalCacheDir() + File.separator + "running_result_1";
//                    try {
//                        FileWriter writer = new FileWriter(path);
//                        for (String line : result) {
//                            writer.write(line);
//                            writer.write("\r\n");
//                        }
//                        writer.flush();
//                        writer.close();
//                    } catch (IOException e) {
//                        e.printStackTrace();
//                    }
//                } else {
//                    Utils.debug("running_result 1 empty");
//                }
//
//                List<String> rr = runRootCommandForResult("dumpsys activity services");
//                if (rr != null && rr.size() > 0) {
//                    Utils.debug("running_result 2 size:" + rr.size());
//                    String path = context.getExternalCacheDir() + File.separator + "running_result_2";
//                    try {
//                        FileWriter writer = new FileWriter(path);
//                        for (String line : rr) {
//                            writer.write(line);
//                            writer.write("\r\n");
//                        }
//                        writer.flush();
//                        writer.close();
//                    } catch (IOException e) {
//                        e.printStackTrace();
//                    }
//                } else {
//                    Utils.debug("running_result 2 empty");
//                }
//
//                Utils.debug("-------mat test end-------");
//            }

            return rInfoList;
        }

    }

    /**
     * @param componentType 0:service 1:receiver 2:activity 3:provider 4:all four types
     */
    public static List<ComponentModel> getComponentModels(Context context, String packageName, int componentType) {
        List<ComponentModel> result = new ArrayList<>();
        try {
            Context targetContext = context.createPackageContext(packageName, 0);
            AssetManager assetManager = targetContext.getAssets();

            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            String sourceDir = context.getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
            int cookie = (int) addAssetPath.invoke(assetManager, sourceDir);
            XmlResourceParser parser = assetManager.openXmlResourceParser(cookie, "AndroidManifest.xml");
            Resources resources = new Resources(assetManager, targetContext.getResources().getDisplayMetrics(), null);

            int type;
            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) {
                if (type == XmlResourceParser.START_TAG) {
                    if ((componentType == 0 || componentType == 4) && "service".equals(parser.getName())
                            || (componentType == 1 || componentType == 4) && "receiver".equals(parser.getName())
                            || (componentType == 2 || componentType == 4) && ("activity".equals(parser.getName()) || "activity-alias".equals(parser.getName()))
                            || (componentType == 3 || componentType == 4) & "provider".equals(parser.getName())) {
//                        String name = parser.getAttributeValue(ANDROID_NAMESPACE, "name");
//                        //cmb.pb招商银行客户端因为乱码可能有问题
//                        //https://github.com/miracle2k/android-autostarts/blob/master/src/com/elsdoerfer/android/autostarts/ReceiverReader.java
//                        if (name == null) {
//                            for (int i = 0; i < parser.getAttributeCount(); i++) {
//                                if (TextUtils.isEmpty(parser.getAttributeName(i))) {
//                                    int res = parser.getAttributeNameResource(i);
//                                    if (res != 0 && resources.getResourceEntryName(res).equals("name")) {
//                                        name = parser.getAttributeValue(i);
//                                        break;
//                                    }
//                                }
//                            }
//                        }

                        String name = Utils.getAttributeValueByName(parser, resources, "name");
                        if (name == null) {
                            continue;
                        }

                        ComponentModel component = new ComponentModel();
                        if (!name.contains(".")) {
                            component.className = packageName + "." + name;
                        } else if (name.startsWith(".")) {
                            component.className = name.startsWith(".") ? packageName + name : name;
                        } else {
                            component.className = name;
                        }
                        component.packageName = packageName;

                        component.enabledInManifest = parser.getAttributeBooleanValue(ANDROID_NAMESPACE, "enabled",true);

                        result.add(component);
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        Collections.sort(result, new Comparator<ComponentModel>() {
            @Override
            public int compare(ComponentModel lhs, ComponentModel rhs) {
                String l = lhs.className
                        .substring(lhs.className.lastIndexOf(".") + 1);
                String r = rhs.className
                        .substring(rhs.className.lastIndexOf(".") + 1);
                return l.compareTo(r);
            }
        });
        return result;
    }

    public static @Nullable String getAttributeValueByName(XmlResourceParser parser, Resources resources, String name) {
        String value = parser.getAttributeValue(ANDROID_NAMESPACE, name);
        //cmb.pb招商银行客户端因为乱码可能有问题
        //https://github.com/miracle2k/android-autostarts/blob/master/src/com/elsdoerfer/android/autostarts/ReceiverReader.java
        if (value == null) {
            for (int i = 0; i < parser.getAttributeCount(); i++) {
                if (TextUtils.isEmpty(parser.getAttributeName(i))) {
                    int res = parser.getAttributeNameResource(i);
                    if (res != 0 && resources.getResourceEntryName(res).equals(name)) {
                        value = parser.getAttributeValue(i);
                        break;
                    }
                }
            }
        }
        return value;
    }

    public static boolean isComponentEnabled(ComponentModel cInfo,
                                             PackageManager pm) {
        int enabled = pm.getComponentEnabledSetting(new ComponentName(
                cInfo.packageName, cInfo.className));
        if (enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
            return cInfo.enabledInManifest;
        }else {
            return enabled <= PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
        }
    }

    /**
     * @see #isRunning
     */
    public static int getRunningNum(String packageName,
                                    List<RunningServiceInfo> runningServiceInfos) {
        int num = 0;
        if (runningServiceInfos != null) {
            for (RunningServiceInfo rInfo : runningServiceInfos) {
                if (rInfo.restarting != 0 || rInfo.pid == 0) {
                    continue;
                }
                ComponentName cName = rInfo.service;
                if (cName != null && TextUtils.equals(cName.getPackageName(), packageName)) {
                    num++;
                }
            }
        }

        return num;
    }

    public static boolean isRunning(String packageName, String className,
                                    List<RunningServiceInfo> rInfos) {
        for (RunningServiceInfo rInfo : rInfos) {
            // FIXME: 2018/3/17 被ifw阻止后会被getRunningServices获取到，但是pid=0 && restarting=0，该service其实没有走生命周期
            //正在重启的时候会有两个，com.netease.cloudmusic.service.PlayService
            if (rInfo.restarting != 0 || rInfo.pid == 0) {
                continue;
            }
            if (rInfo.service != null && rInfo.service.getPackageName().equals(packageName)
                    && rInfo.service.getClassName().equals(className)) {
                return true;
            }
        }
        return false;
    }

    public static boolean checkSDcard(Context context) {
        if (!Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            Toast.makeText(context, R.string.media_not_mounted,
                    Toast.LENGTH_LONG).show();
            return false;
        }

        if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(context, R.string.permission_request_failed, Toast.LENGTH_SHORT).show();
            return false;
        }

        return true;
    }

//	public static boolean runRootCommand(String command) {
//        Process process = null;
//        DataOutputStream os = null;
//        try {
//            process = Runtime.getRuntime().exec("su");
//            os = new DataOutputStream(process.getOutputStream());
//
//            // 类似Titanium的中文高8位会丢失
////             os.writeBytes(command + "\n");
//            os.write(command.getBytes());
//            os.writeBytes("exit\n");
//            os.flush();
//
//            process.waitFor();
//        } catch (Exception e) {
//        	e.printStackTrace();
//            return false;
//        } finally {
//            try {
//                if (os != null) {
//                    os.close();
//                }
//                if (process != null) {
//                    process.destroy();
//                }
//            } catch (Exception e) {
//                // nothing
//            }
//        }
//        return true;
//    }

    /**
     * use root-command-library to get root access
     */
//	public static boolean runRootCommand2(String command) {
//		try {
//			Shell shell=Shell.startRootShell();
//			shell.add(new SimpleCommand(command)).waitForFinish();
//			shell.close();
//		} catch (Exception e) {
//			e.printStackTrace();
//			return false;
//		}
//
//		return true;
//	}

//    public static List<String> runRootCommandForResult(String cmd) {
//        final List<String> stdout = new ArrayList<>();
//        try {
//            Command command = new Command(0, cmd) {
//                @Override
//                public void commandOutput(int id, String line) {
//                    super.commandOutput(id, line);
//                    if (Utils.isTestForDatabase()) {
//                        Log.e("wangqi", line);
//                    }
//                    stdout.add(line);
//                }
//
//                @Override
//                public void commandTerminated(int id, String reason) {
//                    super.commandTerminated(id, reason);
//                    if (Utils.isTestForDatabase()) {
//                        Log.e("wangqi2", reason);
//                    }
//                }
//
//                @Override
//                public void commandCompleted(int id, int exitcode) {
//                    super.commandCompleted(id, exitcode);
//                    if (Utils.isTestForDatabase()) {
//                        Log.e("wangqi3", "exitcode:"+exitcode);
//                    }
//                }
//            };
//
//            RootTools.getShell(true).add(command);
//
//            while (true) {
//                if (command.isFinished()) {
//                    break;
//                }
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        } catch (IOException | TimeoutException | RootDeniedException e) {
//            e.printStackTrace();
//            if (Utils.isTestForDatabase()) {
//                Log.e("wangqi4", cmd + " , result exception:" + (e.toString()));
//            }
//            return null;
//        }
//        if (Utils.isTestForDatabase()) {
//            Log.e("wangqi5", cmd + " , result size:" + (stdout.size()));
//        }
//        return stdout;
//    }

    public static List<String> runRootCommandForResult(String cmd) {
        if (eu.chainfire.libsuperuser.Shell.SU.available()) {
            return eu.chainfire.libsuperuser.Shell.SU.run(cmd);
        }else{
            return null;
        }
    }

    /**
     * 包括stderr\stdout
     */
    public static List<String> runRootCommandForAllResult(String cmd) {
        if (eu.chainfire.libsuperuser.Shell.SU.available()) {
            List<String> result =   eu.chainfire.libsuperuser.Shell.SU.runGetAll(cmd);
            if (BuildConfig.DEBUG) {
                Utils.debug(TAG, "runRootCorunRootCommandForAllResultmmand start:" + cmd);
                int index = 0;
                if (result != null) {
                    for (String s : result) {
                        Utils.debug(TAG, index++ + ":" + s);
                    }
                } else {
                    Utils.debug(TAG, "runRootCommandForAllResult return null");
                }
                Utils.debug(TAG, "runRootCommandForAllResult end:" + cmd);
            }
            return result;
        }else{
            return null;
        }
    }

    public static List<String> runCommandForResult(String cmd) {
        List<String> result =  eu.chainfire.libsuperuser.Shell.SH.run(cmd);
        if (BuildConfig.DEBUG) {
            Utils.debug(TAG, "runCommandForResult start:" + cmd);
            int index = 0;
            if (result != null) {
                for (String s : result) {
                    Utils.debug(TAG, index++ + ":" + s);
                }
            } else {
                Utils.debug(TAG, "runCommandForResult return null");
            }
            Utils.debug(TAG, "runCommandForResult end:" + cmd);
        }
        return result;
    }

    public static boolean runRootCommand(String... cmd) {
        if (eu.chainfire.libsuperuser.Shell.SU.available()) {
            List<String> result = eu.chainfire.libsuperuser.Shell.SU.run(cmd);
            if (BuildConfig.DEBUG) {
                String cmdStr = Arrays.toString(cmd);
                Utils.debug(TAG, "runRootCommand start:" + cmdStr);
                if (result != null) {
                    int index = 0;
                    for (String s : result) {
                        Utils.debug(TAG, index++ + ":" + s);
                    }
                } else {
                    Utils.debug(TAG, "runRootCommand return null");
                }

                Utils.debug(TAG, "runRootCommand end:" + cmdStr);
            }
            return result != null;
        }else{
            if (BuildConfig.DEBUG) {
                Utils.debug(TAG, "su is not available");
            }
            return false;
        }
    }

//    public static List<String> runCommandForResult(String cmd) {
//        final List<String> stdout = new ArrayList<>();
//        try {
//            Command command = new Command(0, cmd) {
//                @Override
//                public void commandOutput(int id, String line) {
//                    super.commandOutput(id, line);
//                    stdout.add(line);
//                }
//
//                @Override
//                public void commandTerminated(int id, String reason) {
//                    super.commandTerminated(id, reason);
//                }
//
//                @Override
//                public void commandCompleted(int id, int exitcode) {
//                    super.commandCompleted(id, exitcode);
//                }
//            };
//
//            RootTools.getShell(false).add(command);
//
//            while (true) {
//                if (command.isFinished()) {
//                    break;
//                }
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        } catch (IOException | TimeoutException | RootDeniedException e) {
//            e.printStackTrace();
//            return null;
//        }
//        return stdout;
//    }

//    public static boolean runRootCommand(String... cmd) {
//        try {
//            //Command command = new Command(0, cmd);
//            Command command;
//            if(Utils.isTestForUninstallSysApp()){
//                if (Utils.isTestForDatabase()) {
//                    Log.e("wangqi0", Arrays.toString(cmd));
//                }
//                command = new Command(0, cmd){
//                    @Override
//                    public void commandOutput(int id, String line) {
//                        super.commandOutput(id, line);
//                        if (Utils.isTestForDatabase()) {
//                            Log.e("wangqi", line);
//                        }
//                    }
//
//                    @Override
//                    public void commandTerminated(int id, String reason) {
//                        super.commandTerminated(id, reason);
//                        if (Utils.isTestForDatabase()) {
//                            Log.e("wangqi2", reason);
//                        }
//                    }
//
//                    @Override
//                    public void commandCompleted(int id, int exitcode) {
//                        super.commandCompleted(id, exitcode);
//                        if (Utils.isTestForDatabase()) {
//                            Log.e("wangqi3", "exitcode:"+exitcode);
//                        }
//                    }
//                };
//            }else{
//                command = new Command(0, cmd);
//            }
//
//
//            Shell shell = RootTools.getShell(true);
//            shell.add(command);
//
//            while (true) {
//                if (command.isFinished()) {
//                    break;
//                }
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//            return command.getExitCode() == 0;
//        } catch (IOException | TimeoutException | RootDeniedException e) {
//            e.printStackTrace();
//            return false;
//        }
//    }

    //https://github.com/shadowsocks/shadowsocks-android/issues/826
    //http://blog.csdn.net/zyp009/article/details/17397383
    // 例如/system/app/Email/下面有oat目录，里面有odex，在UninstalledRecyclerListFragment卸载时才删除
    /**
     * 已经mount system rw了，只需要后面unmount
     */
    public static String getUnmountSystemCommand(Context context) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        int mountSysMode = preferences.getInt("mountSysMode", 0);
        if (mountSysMode == 1) {
            String command1 = Utils.getUnmountSystemCommand1(preferences, mountSysMode);
            if (command1 != null) {
                return command1;
            }
        } else if (mountSysMode == 2) {
            String command2 = Utils.getUnmountSystemCommand2(preferences, mountSysMode);
            if (command2 != null) {
                return command2;
            }
        }

        //Device or resource busy 是err
        List<String> mountSysResult = Utils.runRootCommandForAllResult("mount -o remount,rw /system");
        if (mountSysResult == null || mountSysResult.size() == 0) {
            if (mountSysMode != 0) {
                preferences.edit().putInt("mountSysMode", 0).apply();
            }
            return "mount -o remount,ro /system";
        }
        if (mountSysResult.size() > 0) {
            if (mountSysResult.get(0).endsWith("Device or resource busy")) {
                //从mount命令的返回结果中找
                //每个版本返回的格式不一样，先直接去第一个空格之前的吧
                ///dev/block/platform/msm_sdcc.1/by-name/system on /system type ext4 (ro,seclabel,noatime,data=ordered)
                //用root命令返回的不一样，而且有问题
                String command1 = Utils.getUnmountSystemCommand1(preferences, mountSysMode);
                if (command1 != null) {
                    return command1;
                }

                //从/proc/mounts中获取
                String command2 = Utils.getUnmountSystemCommand2(preferences, mountSysMode);
                if (command2 != null) {
                    return command2;
                }
            }else{
                com.tencent.mars.xlog.Log.e(TAG, "normal mount0 sys return:" + TextUtils.join(",", mountSysResult));
            }
        }
        preferences.edit().putInt("mountSysMode", 0).apply();
        return null;

//        String mountCommand = Build.VERSION.SDK_INT>=24? "mount -o rw,remount /dev/block/platform/msm_sdcc.1/by-name/system /system" :"mount -o remount,rw /system";
//        String unmountCommand = Build.VERSION.SDK_INT>=24? "mount -o ro,remount /dev/block/platform/msm_sdcc.1/by-name/system /system" :"mount -o remount,ro /system";
//
//        //每个版本返回的格式不一样，先直接去第一个空格之前的吧
//        ///dev/block/platform/msm_sdcc.1/by-name/system on /system type ext4 (ro,seclabel,noatime,data=ordered)
//        if (Build.VERSION.SDK_INT >= 24) {
//            //用root命令返回的不一样，而且有问题
//            List<String> mountList= Utils.runCommandForResult("mount |grep '/system '");
//            if (mountList != null && mountList.size() > 0) {
//                if (mountList.size() > 1) {
//                    Log.e("matpro", "get system mount size>1," + TextUtils.join(",", mountList));
//                }
//                String location = mountList.get(0).substring(0, mountList.get(0).indexOf(" "));
//
//                mountCommand = "mount -o rw,remount " + location +" /system";
//                unmountCommand = "mount -o ro,remount " + location+" /system";
//            }
//        }
//
//        return new String[]{mountCommand, unmountCommand};
    }

    private static String getUnmountSystemCommand2(SharedPreferences preferences,int mode){
        //从/proc/mounts中获取
        List<String> mountList= Utils.runCommandForResult("cat /proc/mounts |grep '/system '");
        if (mountList != null && mountList.size() > 0) {
            String location = mountList.get(0).substring(0, mountList.get(0).indexOf(" "));
            String mountCommand = "mount -o rw,remount " + location +" /system";
            List<String> mountSysResult = Utils.runRootCommandForResult(mountCommand);
            if (mountSysResult == null || mountSysResult.size() == 0) {
                if (mode != 2) {
                    preferences.edit().putInt("mountSysMode", 2).apply();
                }
                return "mount -o ro,remount " + location +" /system";
            }else{
                com.tencent.mars.xlog.Log.e(TAG, "mount2 sys return:" + TextUtils.join(",", mountSysResult));
            }
        }
        return null;
    }

    /**
     * 执行一个或者多个pm enable/disable 命令，要注意组件不存在时会报错 java.lang.IllegalArgumentException: Unknown component: ComponentInfo{com.zhihu.android/com.avos.avoscloud.PushService}
     * @param command
     * @return 正常成功1、正常失败-1，组件不存在的失败-2
     */
    public static int runMultiPmDisableCommand(String command){
        final List<String> ss = eu.chainfire.libsuperuser.Shell.SU.runGetOnlyErr(command);
        final Pattern pattern = Pattern.compile("[\\w\\W]+Component class [^ ]+ does not exist in [^ ]+");
        //禁用多个时 只有最后一个不存在时会影响process.exitValue()返回1
        if (ss != null && ss.size() > 0 && pattern.matcher(ss.get(ss.size() - 1)).matches()) {
            return -2;
        }else {
            return ss != null ? 1 : -1;
        }
    }

    //https://android.stackexchange.com/a/158890/228460
    private static String getUnmountSystemCommand1(SharedPreferences preferences,int mode){
        //从mount命令的返回结果中找
        //每个版本返回的格式不一样，先直接去第一个空格之前的吧
        ///dev/block/platform/msm_sdcc.1/by-name/system on /system type ext4 (ro,seclabel,noatime,data=ordered)
        //用root命令返回的不一样，而且有问题
        List<String> mountList= Utils.runCommandForResult("mount |grep '/system '");
        if (mountList != null && mountList.size() > 0) {
            if (mountList.size() > 1) {
                com.tencent.mars.xlog.Log.e(TAG, "get system mount size>1," + TextUtils.join(",", mountList));
            }
            String location = mountList.get(0).substring(0, mountList.get(0).indexOf(" "));
            String mountCommand = "mount -o rw,remount " + location + " /system";
            List<String>mountSysResult = Utils.runRootCommandForResult(mountCommand);
            if (mountSysResult == null || mountSysResult.size() == 0) {
                if (mode != 1) {
                    preferences.edit().putInt("mountSysMode", 1).apply();
                }
                return "mount -o ro,remount " + location + " /system";
            } else {
                com.tencent.mars.xlog.Log.e(TAG, "mount1 sys return:" + TextUtils.join(",", mountSysResult));
            }
        }
        return null;
    }

//    public static Locale getSystemLocale() {
//        if (Build.VERSION.SDK_INT >= 24) {
//            return Resources.getSystem().getConfiguration().getLocales().get(0);
//        } else {
//            return Resources.getSystem().getConfiguration().locale;
//        }
//    }

    public static int dp2px(Context context,int dp) {
        return Math.round(context.getResources().getDisplayMetrics().density * dp);
    }

    public static void debug(String msg) {
        if ((BuildConfig.DEBUG || isDebugForUser()) && !TextUtils.isEmpty(msg)) {
            com.tencent.mars.xlog.Log.e("matpro", msg);
        }
    }
    public static void debug(String tag,String msg) {
        if ((BuildConfig.DEBUG || isDebugForUser()) && !TextUtils.isEmpty(msg)) {
            com.tencent.mars.xlog.Log.d(tag, msg);
        }
    }
    public static void err(String tag, String msg, Exception e) {
        if ((BuildConfig.DEBUG || isDebugForUser()) && !TextUtils.isEmpty(msg)) {
            Log.e(tag, msg, e);
        }
    }

    public static boolean isDebugForUser() {
        return false;
    }

    public static boolean isTestForWindowManager() {
        return false;
    }
    public static boolean isTestForDatabase(){
        return false;
    }
    public static boolean isTestCurrentFragment(){
        return false;
    }

    public static boolean isTestForUninstallSysApp(){
        return true;
    }

    public static boolean isRemoveAccessibilityForGoogle(){
        return true;
    }
}

app/src/main/java/cn/wq/myandroidtoolspro/model/ComponentEntry.java
public class ComponentEntry extends ComponentModel {
	public boolean enabled;
}

app/src/main/java/cn/wq/myandroidtoolspro/model/ReceiverEntry.java
public class ReceiverEntry extends ComponentEntry{
	public String actions;
}

app/src/main/java/cn/wq/myandroidtoolspro/recyclerview/multi/MultiSelectionUtils.java
public class MultiSelectionUtils {
    public static Controller attach(MultiSelectionRecyclerListFragment fragment) {
        return Controller.attach(fragment);
    }

    public static class Controller implements ActionMode.Callback,RecyclerListView.OnRecyclerItemClickListener{
        private RecyclerListView mRecyclerListView;
        private AppCompatActivity mActivity;
        private ActionMode mActionMode;
        private ArrayList<Integer> mItemsSelected;
        private MultiSelectionRecyclerListFragment mFragment;

        private Controller() {
        }

        private static Controller attach(MultiSelectionRecyclerListFragment fragment) {
            Controller controller = new Controller();
            controller.mActivity= (AppCompatActivity) fragment.getActivity();
            controller.mRecyclerListView=fragment.getRecyclerListView();
            controller.mFragment=fragment;
            return controller;
        }

        public boolean isInActionMode() {
            return mActionMode != null;
        }

        public void startActionMode() {
            if (mActionMode == null) {
                mItemsSelected = new ArrayList<>();
                mActionMode=mActivity.startSupportActionMode(this);
            }
        }

        public void finish() {
            if (mActionMode != null) {
                mActionMode.finish();
                mActionMode = null;
            }
        }

        private String getStateKey() {
            return MultiSelectionUtils.class.getSimpleName() + "_" + mRecyclerListView.getId();
        }
        public void saveInstanceState(Bundle outState) {
            if (mActionMode != null) {
                outState.putIntegerArrayList(getStateKey(),mItemsSelected);
            }
        }

        public void restoreInstanceState(Bundle savedInstanceState) {
            mItemsSelected=null;
            if (savedInstanceState != null) {
                mItemsSelected = savedInstanceState.getIntegerArrayList(getStateKey());
            }

            RecyclerView.Adapter adapter=mRecyclerListView.getAdapter();
            if (mItemsSelected == null||adapter==null) {
                return;
            }
            startActionMode();
        }

        public void onStateChanged(int position,boolean selected) {
            int index=mItemsSelected.indexOf(position);
            if(selected && index<0){
                mItemsSelected.add(position);
            }else if(!selected && index>=0){
                mItemsSelected.remove(index);
                if (mItemsSelected.size() == 0) {
                    finish();
                }
            }
        }

        public ArrayList<Integer> getSelectedItemsPosition() {
            return mItemsSelected;
        }

        public boolean isSelectedAtPosition(int position) {
            return mItemsSelected != null && mItemsSelected.contains(position);
        }

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            if (Build.VERSION.SDK_INT >= 21) {
                mActivity.getWindow().setStatusBarColor(ContextCompat.getColor(mActivity, R.color.blue_grey_700));
            }
            if(mFragment!=null && mFragment.onCreateActionMode(mode,menu)){
                if (mItemsSelected != null && mItemsSelected.size() > 0) {
                    for(int i=0;i<mItemsSelected.size();i++) {
                        MultiSelectableViewHolder viewHolder= (MultiSelectableViewHolder) mRecyclerListView.findViewHolderForLayoutPosition(mItemsSelected.get(i));
                        viewHolder.setSelected(true);
                    }
                }
                return true;
            }
            return false;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return mFragment!=null && mFragment.onPrepareActionMode(mode, menu);
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            return mFragment != null && mFragment.onActionItemClicked(mode, item);
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            if (Build.VERSION.SDK_INT >= 21) {
                mActivity.getWindow().setStatusBarColor(ContextCompat.getColor(mActivity, android.R.color.transparent));
            }
            if (mFragment != null) {
                mFragment.onDestroyActionMode(mode);
            }
            if (mItemsSelected != null && mItemsSelected.size() > 0) {
                for(int i=0;i<mItemsSelected.size();i++) {
                    MultiSelectableViewHolder viewHolder= (MultiSelectableViewHolder) mRecyclerListView.findViewHolderForLayoutPosition(mItemsSelected.get(i));
                    if (viewHolder != null) {
                        viewHolder.setSelected(false);
                    }
                }
                mItemsSelected.clear();
            }
            mActionMode=null;
        }

        @Override
        public void onItemClick(int position, View v) {
            if (mFragment != null) {
                mFragment.onItemClick(position,v);
            }
        }
    }

}

app/src/main/java/cn/wq/myandroidtoolspro/recyclerview/adapter/AbstractComponentAdapter.java
public abstract class AbstractComponentAdapter<T extends ComponentEntry>
        extends RecyclerView.Adapter
        implements Filterable{
    private List<T> list;
    private boolean isFullName;
    private ComponentFilter mFilter;
    private List<T> originalData;
    private final Object mLock = new Object();
    protected Context mContext;
    protected int primaryTextColor;
    protected int redTextColor;

//    public AbstractComponentAdapter() {
//        super();
//        this.list = new ArrayList<>();
//    }

    public AbstractComponentAdapter(Context context) {
        this.list = new ArrayList<>();
        mContext = context;
        primaryTextColor = Utils.getColorFromAttr(context, android.R.attr.textColorPrimary);
        redTextColor = ContextCompat.getColor(mContext, R.color.holo_red_light);
    }

    public T getItem(int position) {
        return list.get(position);
    }

    public void setData(List<T> list) {
        this.list.clear();
        if (list != null) {
            this.list.addAll(list);
        }

        //wq:搜索后禁用 刷新状态
        if(originalData!=null){
            originalData.clear();
            originalData=null;
        }

        notifyDataSetChanged();
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public boolean toggleName() {
        isFullName = !isFullName;
        notifyDataSetChanged();
        return isFullName;
    }

    public boolean getIsFullName() {
        return isFullName;
    }

    @Override
    public Filter getFilter() {
        if (mFilter == null) {
            mFilter = new ComponentFilter();
        }
        return mFilter;
    }

    private class ComponentFilter extends Filter {
        @Override
        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {
            list = (List<T>) results.values;
            notifyDataSetChanged();
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            final FilterResults results = new FilterResults();
            if (originalData == null) {
                synchronized (mLock) {
                    originalData = new ArrayList<>(list);
                }
            }

            List<T> tempList;
            if (TextUtils.isEmpty(constraint)||constraint.toString().trim().length()==0) {
                synchronized (mLock) {
                    tempList = new ArrayList<>(originalData);
                }
                results.values = tempList;
                results.count = tempList.size();
            } else {
                synchronized (mLock) {
                    tempList = new ArrayList<>(originalData);
                }

                final List<T> newValues = new ArrayList<>();
                int type=-1;
                for (T entry : tempList) {
                    if(type<0){
                        type=0;
                        if(entry instanceof ReceiverWithActionEntry){
                            type=1;
                        }
                    }

                    //最好trim一下
                    String query=constraint.toString().trim().toLowerCase(Locale.getDefault());
                    String lowerName=entry.className.toLowerCase(Locale.getDefault());
                    if ((isFullName && lowerName.contains (query)
                            || (!isFullName && lowerName.substring(lowerName.lastIndexOf(".")+1).contains(query)))){
                        newValues.add(entry);
                    }else if(type>0){
                        ReceiverWithActionEntry receiverEntry= (ReceiverWithActionEntry) entry;
                        if(receiverEntry.appName.toLowerCase(Locale.getDefault()).contains(query)){
                            newValues.add(entry);
                        }
                    }
                }

                results.values = newValues;
                results.count = newValues.size();
            }

            return results;
        }
    }

}

app/src/main/java/cn/wq/myandroidtoolspro/helper/IfwUtil.java
public class IfwUtil {
    private static final String TAG = "IfwUtil";
    private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";

    public static final int COMPONENT_FLAG_EMPTY = 0x00;
    public static final int COMPONENT_FLAG_ACTIVITY = 0x01;
    public static final int COMPONENT_FLAG_RECEIVER = 0x10;
    public static final int COMPONENT_FLAG_SERVICE = 0x100;
    public static final int COMPONENT_FLAG_ALL = 0x111;

    public static final String BACKUP_LOCAL_FILE_EXT = ".ifw";
    public static final String BACKUP_SYSTEM_FILE_EXT_OF_STANDARD = ".xml"; //标准ifw备份后缀
    public static final String BACKUP_SYSTEM_FILE_EXT_OF_MINE = "$" + BACKUP_SYSTEM_FILE_EXT_OF_STANDARD;    //本App备份ifw使用的后缀

    /**
     * 组件列表中修改选中位置component的ifw状态，并保存所有component到ifw文件<br>
     * 每个类型的list中（例如ServiceRecyclerListFragment）的修改都需要完整保存整个ifw内容
     *
     * @param positions 某一类component的位置
     */
    public static boolean saveComponentIfw(Context context,
                                           @NonNull String packageName,
                                           IfwEntry ifwEntry,
                                           @NonNull AbstractComponentAdapter<? extends ComponentEntry> adapter,
                                           int cType,
                                           boolean useParentIfw,
                                           Integer... positions) {
        return saveComponentIfw(context, packageName, ifwEntry, adapter, cType, useParentIfw, null, positions);
    }

    /**
     * 组件列表中修改选中位置component的ifw状态，并保存所有component到系统ifw文件（先保存到本地临时文件）<br>
     * 每个类型的list中（例如ServiceRecyclerListFragment）的修改都需要完整保存整个ifw内容
     * @param localTempDir  所有app的ifw文件已经都复制到本地的目录，供组件全局禁用使用，{@link cn.wq.myandroidtoolspro.recyclerview.fragment.SearchComponentInAllFragment}
     * @param positions     某一类component的位置
     */
    public static boolean saveComponentIfw(Context context,
                                           @NonNull String packageName,
                                           IfwEntry ifwEntry,
                                           @NonNull AbstractComponentAdapter<? extends ComponentEntry> adapter,
                                           int cType,
                                           boolean useParentIfw,
                                           @Nullable File localTempDir,
                                           Integer... positions
                                           ) {
        if (positions == null || positions.length == 0) {
            //不应该为空
            return true;
        }
        if (ifwEntry == null || ifwEntry == IfwEntry.EMPTY) {
            ifwEntry = new IfwEntry();
            if (useParentIfw) {
                AppInfoForManageFragment2.mIfwEntry = ifwEntry;
            }
        }
        Map<String, Set<String>> componentMap = getComponentMapInIfw(cType, ifwEntry);
        ComponentEntry entry;
        for (Integer pos : positions) {
            entry = adapter.getItem(pos);
            if (entry == null) {
                continue;
            }
            String cls = entry.className;
            if (cls.startsWith(".")) {
                cls = entry.packageName + cls;
            }
            Set<String> componentSet = componentMap.get(packageName);
            if (componentSet == null) {
                componentSet = new HashSet<>();
                componentMap.put(packageName, componentSet);
            }
            if (entry.isIfwed) {
                componentSet.remove(cls);
            } else {
                componentSet.add(cls);
            }
        }

        return saveIfwEntryToSystemFile(context, packageName, ifwEntry, localTempDir);
    }

    /**
     * 备份使用,先保存一个app下的禁用组件到本地文件，最后一次cp到系统目录中
     */
    public static void saveComponentIfwToLocal(@NonNull String packageName,
                                               List<BackupEntry> list,
                                               @NonNull String tempDir
    ) throws IOException {
        if (list == null) {
            return;
        }
        IfwEntry ifwEntry = new IfwEntry();
        for (BackupEntry entry : list) {
            Map<String, Set<String>> componentMap = getComponentMapInIfw(entry.cType, ifwEntry);
            Set<String> componentSet = componentMap.get(packageName);
            if (componentSet == null) {
                componentSet = new HashSet<>();
                componentMap.put(packageName, componentSet);
            }
            componentSet.add(entry.className);
        }
        saveIfwEntryToLocalFile(packageName, ifwEntry, tempDir);
    }

    private static void buildIfwEntryToString(StringBuilder sBuilder, @NonNull String packageName, @NonNull IfwEntry ifwEntry) {
        buildIfwEntryToString(sBuilder, packageName, ifwEntry.serviceMap, "service");
        buildIfwEntryToString(sBuilder, packageName, ifwEntry.activityMap, "activity");
        buildIfwEntryToString(sBuilder, packageName, ifwEntry.receiverMap, "broadcast");
    }

    /**
     * @param sBuilder
     * @param packageName
     * @param componentMap
     * @param tagName      service,activity,broadcast
     */
    private static void buildIfwEntryToString(StringBuilder sBuilder, @NonNull String packageName, Map<String, Set<String>> componentMap, String tagName) {
        if (componentMap != null && componentMap.size() > 0) {
            Set<String> set = componentMap.get(packageName);
            if (set != null && set.size() > 0) {
                sBuilder.append("  <").append(tagName).append(" block=\"true\" log=\"false\">\n");
                for (String className : set) {
                    String cls = className;
                    if (className.startsWith(packageName)) {
                        cls = "." + className.substring(packageName.length() + 1);
                    }
                    sBuilder.append("    <component-filter name=\"")
                            .append(packageName)
                            .append("/")
                            .append(cls)
                            .append("\" />\n");
                }
                sBuilder.append("  </").append(tagName).append(">\n");
            }
        }
    }

    /**
     * 保存IfwEntry中指定packageName的数据到系统文件
     * @param localTempDir 可以为空；为空时使用 getExternalCacheDir()
     */
    private static boolean saveIfwEntryToSystemFile(Context context, @NonNull String packageName, @NonNull IfwEntry ifwEntry,@Nullable File localTempDir) {
        StringBuilder sBuilder = new StringBuilder("<rules>\n");
        buildIfwEntryToString(sBuilder, packageName, ifwEntry);
        sBuilder.append("</rules>\n");

        return saveIfwStringToSystemFile(context, sBuilder.toString(), packageName, localTempDir);
    }

    /**
     * 保存IfwEntry到我自定义后缀的系统文件
     */
    public static boolean saveIfwEntryToMySystemFile(Context context, @NonNull IfwEntry ifwEntry) {
        Set<String> packageNameSet = new HashSet<>(ifwEntry.serviceMap.keySet());
        packageNameSet.addAll(ifwEntry.receiverMap.keySet());
        packageNameSet.addAll(ifwEntry.activityMap.keySet());
        Utils.debug(TAG, "saveIfwEntryToMySystemFile:" + packageNameSet.toString()+","+ifwEntry.serviceMap.keySet().toString()+","
            +ifwEntry.receiverMap.keySet()+","+ifwEntry.activityMap.keySet());
        for (String pkg : packageNameSet) {
            boolean result = saveIfwEntryToSystemFile(context, pkg, ifwEntry, null);
            if (!result) {
                return false;
            }
        }
        return true;
    }

    /**
     * 保存IfwEntry到本地文件
     */
    private static void saveIfwEntryToLocalFile(
            @NonNull String packageName,
            @NonNull IfwEntry ifwEntry,
            String destDir) throws IOException {
        StringBuilder sBuilder = new StringBuilder("<rules>\n");
        buildIfwEntryToString(sBuilder, packageName, ifwEntry);
        sBuilder.append("</rules>\n");

        saveIfwStringToLocalFile(sBuilder.toString(), packageName, destDir);
    }

    /**
     * 保存指定包名的ifw内容到系统文件，文件名格式为：packageName$.xml
     *
     * @param content ifw内容
     * @param pkgName app包名
     */
    private static boolean saveIfwStringToSystemFile(
            Context context,
            String content,
            @NonNull String pkgName,
            @Nullable File localTempDir
    ) {
        String tempRelativePath = getLocalRelativeTempIfwPath(context, pkgName, localTempDir);
        File tempFile = new File(Environment.getExternalStorageDirectory(), tempRelativePath);
        String destPath = getSystemIfwDir() + File.separator + pkgName + IfwUtil.BACKUP_SYSTEM_FILE_EXT_OF_MINE;
        FileWriter writer = null;
        try {
            writer = new FileWriter(tempFile, false);
            writer.write(content);
            writer.flush();
            writer.close();
            boolean result = copy(tempRelativePath, destPath);
            if (result) {
                return true;
            }
        } catch (Exception e) {
            Log.e(TAG, "saveIfwStringToSystemFile error", e);
            Utils.err(TAG, "saveIfwStringToSystemFile error", e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //复制到/data/system/ifw下失败，得把临时文件删了
        if (tempFile.exists()) {
            tempFile.delete();
        }
        return false;
    }

    /**
     * 保存指定包名的ifw内容到本地文件，文件名格式为：packageName$.xml
     *
     * @param content ifw内容
     * @param pkgName app包名
     * @param destDir 存放ifw文件的目录
     */
    private static void saveIfwStringToLocalFile(String content, @NonNull String pkgName, String destDir) throws IOException {
        String tempPath = destDir + File.separator + pkgName + IfwUtil.BACKUP_SYSTEM_FILE_EXT_OF_MINE;
        FileWriter writer = null;
        try {
            writer = new FileWriter(tempPath, false);
            writer.write(content);
            writer.flush();
            writer.close();
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * file path to file path
     * @param srcRelativePath 相对/sdcard路径
     */
    private static boolean copy(String srcRelativePath, String destPath) {
        boolean cpResult = Utils.runRootCommand("cp -f $EXTERNAL_STORAGE/" + srcRelativePath + " " + destPath + " \n")
                || Utils.runRootCommand("cat $EXTERNAL_STORAGE/" + srcRelativePath + " > " + destPath + " \n");
        if (!cpResult) {
            return false;
        }
        return Utils.runRootCommand("chmod 644 " + destPath);
    }

    /**
     * all files in srsDir to destDir
     * <br>必须用 cp -R src/. dest 才能复制目录下面的所有文件
     */
    public static boolean copySysIfwToTempDir(File destDir) {
        String srcDir = IfwUtil.getSystemIfwDir();
        String tempRelativePath = getLocalRelativeTempIfwPath(destDir);
        return Utils.runRootCommand("cp -fR " + srcDir + "/. $EXTERNAL_STORAGE/" + tempRelativePath);
    }

    /**
     * all files in srsDir to destDir
     * <br>必须用 cp -R src/. dest 才能复制目录下面的所有文件
     */
    public static boolean copyToIfwDir(File srcDir) {
        String destDir = IfwUtil.getSystemIfwDir();
        String tempRelativePath = getLocalRelativeTempIfwPath(srcDir);
        if (!Utils.runRootCommand("cp -fR $EXTERNAL_STORAGE/" + tempRelativePath + "/. " + destDir)) {
            return false;
        }
        String chmodCmd = String.format("find %s -name '*%s'|xargs chmod 644",
                destDir, IfwUtil.BACKUP_SYSTEM_FILE_EXT_OF_MINE);
        return Utils.runRootCommand(chmodCmd);
    }

    private static boolean isEncryptedFilesystemEnabled() {
        try {
            return (boolean) Class.forName("android.os.SystemProperties")
                    .getMethod("getBoolean", String.class, boolean.class)
                    .invoke(null, SYSTEM_PROPERTY_EFS_ENABLED, false);
        } catch (Exception e) {
            Log.e(TAG, "isEncryptedFilesystemEnabled error", e);
            Utils.err(TAG, "isEncryptedFilesystemEnabled error", e);
            return false;
        }
    }

    private static String getDirectoryPath(String variableName, String defaultPath) {
        String path = System.getenv(variableName);
        return path == null ? defaultPath : path;
    }

    public static String getSystemIfwDir() {
        String baseDir;
        if (isEncryptedFilesystemEnabled()) {
            baseDir = getDirectoryPath("ANDROID_SECURE_DATA", "/data/secure");
        } else {
            baseDir = getDirectoryPath("ANDROID_DATA", "/data");
        }
        return baseDir + File.separator + "system" + File.separator + "ifw";
    }

    /**
     * 加载指定包名的ifw文件
     *
     * @param cType 暂时都是all
     * @param loadLocalFirst 之前已加载到本地了，可以先加载本地文件
     */
    public static IfwEntry loadIfwFileForPkg(Context context, @NonNull String packageName, int cType, boolean loadLocalFirst) throws Exception {
        String tempRelativePath = getLocalRelativeTempIfwPath(context, packageName);
        if (!loadLocalFirst) {
//            String command = String.format("ls %s | grep '%s%s'", getSystemIfwDir(), packageName, "\\" + IfwUtil.BACKUP_SYSTEM_FILE_EXT_OF_MINE);
//            List<String> files = Utils.runRootCommandForResult(command);
//            if (files == null || files.size() == 0) {
//                return IfwEntry.EMPTY;
//            }
//            String sourcePath = getSystemIfwDir() + File.separator + files.get(0);
//            String catCmd = String.format("cat %s > $EXTERNAL_STORAGE/%s", sourcePath, tempRelativePath);
//            if (!Utils.runRootCommand(catCmd)) {
//                return IfwEntry.ROOT_ERROR;
//            }

            //下面第一个命令有问题，文件不存在时不会终止命令
//            String command = String.format("cat `find %s -name '%s'|head -1` > $EXTERNAL_STORAGE/%s",
            String command = String.format("find %s -name '%s' | xargs cat > $EXTERNAL_STORAGE/%s",
                    getSystemIfwDir(),
                    packageName + "\\" + IfwUtil.BACKUP_SYSTEM_FILE_EXT_OF_MINE,
                    tempRelativePath);
            if (!Utils.runRootCommand(command)) {
                return IfwEntry.ROOT_ERROR;
            }
        }

        return parseIfwFile(cType, packageName, new File(Environment.getExternalStorageDirectory(),tempRelativePath));
    }

    /**
     * 加载所有ifw文件
     *
     * @param loadLocalFirst 之前已加载到本地了，可以先加载本地文件
     */
    public static IfwEntry loadAllIfwFile(Context context, int cType, File ifwTempDir,boolean loadLocalFirst) throws Exception {
        //临时目录
        if (!ifwTempDir.exists()) {
            ifwTempDir.mkdirs();
        }

        if (!loadLocalFirst) {
            if(!copySysIfwToTempDir(ifwTempDir)){
                return IfwEntry.ROOT_ERROR;
            }
        }

        IfwEntry ifw = new IfwEntry();
        for (File f : ifwTempDir.listFiles()) {
            if (f.isDirectory()) {
                f.delete();
                continue;
            }

            if (!loadLocalFirst) {
                if (!f.getName().endsWith(IfwUtil.BACKUP_SYSTEM_FILE_EXT_OF_MINE)) {
                    f.delete();
                    continue;
                } else {
                    //复制全部系统ifw文件时没法去掉$号，只能这时候去掉
                    String name = f.getName().substring(0, f.getName().length() - BACKUP_SYSTEM_FILE_EXT_OF_MINE.length()) + BACKUP_LOCAL_FILE_EXT;
                    File newF = new File(f.getParent(), name);
                    if (newF.exists()) {
                        newF.delete();
                    }
                    f.renameTo(newF);
                    parseIfwFile(ifw, cType, null, newF);
                }
            } else {
                if (!f.getName().endsWith(BACKUP_LOCAL_FILE_EXT)) {
                    f.delete();
                    continue;
                } else {
                    parseIfwFile(ifw, cType, null, f);
                }
            }

        }

        return ifw;
    }

    private static Map<String, Set<String>> getComponentMapInIfw(int cType, IfwEntry ifwEntry) {
        Map<String, Set<String>> componentMap = null;
        if (cType == COMPONENT_FLAG_SERVICE) {
            componentMap = ifwEntry.serviceMap;
        } else if (cType == COMPONENT_FLAG_ACTIVITY) {
            componentMap = ifwEntry.activityMap;
        } else if (cType == COMPONENT_FLAG_RECEIVER) {
            componentMap = ifwEntry.receiverMap;
        }
        return componentMap;
    }

    public static boolean isComponentInIfw(@NonNull String packageName, String className, int cType, IfwEntry ifwEntry) {
        if (ifwEntry == null || ifwEntry.status < 0) {
            return false;
        }

        Map<String, Set<String>> componentMap = getComponentMapInIfw(cType, ifwEntry);

        return componentMap != null
                && componentMap.get(packageName) != null
                && componentMap.get(packageName).contains(className);
    }

    /**
     * cache目录下packageName.ifw文件
     * @see #getLocalRelativeTempIfwPath(Context, String)
     */
//    private static String getLocalTempIfwPath(Context context, @NonNull String packageName) {
//        return context.getExternalCacheDir() + File.separator + packageName + BACKUP_LOCAL_FILE_EXT;
//    }

    /**
     * 外置cache目录下packageName.ifw文件的相对/sdcard路径
     * shell中获取的/sdcard路径可能和api获取的不一致，且互相不可见：http://www.cloudchou.com/android/post-689.html
     * /storage/emulated/legacy vs  /storage/emulated/0
     */
    private static String getLocalRelativeTempIfwPath(Context context, @NonNull String packageName) {
        return getLocalRelativeTempIfwPath(context, packageName, null);
    }

    /**
     * 外置cache目录下（或者指定临时目录下）packageName.ifw文件的相对/sdcard路径
     * shell中获取的/sdcard路径可能和api获取的不一致，且互相不可见：http://www.cloudchou.com/android/post-689.html
     * /storage/emulated/legacy vs  /storage/emulated/0
     */
    private static String getLocalRelativeTempIfwPath(Context context, @NonNull String packageName,@Nullable File localTempDir) {
        File dir = localTempDir;
        if (localTempDir == null) {
            dir = context.getExternalCacheDir();
        }
        File full = new File(dir, packageName + BACKUP_LOCAL_FILE_EXT);
        return getLocalRelativeTempIfwPath(full);
    }

    /**
     * cache目录下packageName.ifw文件的相对/sdcard路径
     * shell中获取的/sdcard路径可能和api获取的不一致，且互相不可见：http://www.cloudchou.com/android/post-689.html
     * /storage/emulated/legacy vs  /storage/emulated/0
     */
    private static String getLocalRelativeTempIfwPath(File file) {
        return Environment.getExternalStorageDirectory().toURI().relativize(file.toURI()).getPath();
    }

    /**
     * 重构方法，省略IfwEntry的传入
     * @see #parseIfwFile(IfwEntry, int, String, File)
     */
    public static IfwEntry parseIfwFile(int cType,@Nullable String packageName, File tempPath) throws Exception {
        return parseIfwFile(null, cType, packageName, tempPath);
    }

    /**
     * 从ifw文件(tempPath)中读数据到entry中，可以不限包名
     * @param ifw 可以为空，最后会返回最新的IfwEntry
     */
    public static IfwEntry parseIfwFile(@Nullable IfwEntry ifw, int cType, @Nullable String packageName,@NonNull File tempPath) throws Exception {
        if (ifw == null) {
            ifw = new IfwEntry();
        }
        XmlPullParser parser = Xml.newPullParser();
        FileInputStream in = new FileInputStream(tempPath);
        parser.setInput(in, "UTF-8");
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
            if (type == XmlPullParser.START_TAG && !"rules".equals(parser.getName())) {
                if (((cType & COMPONENT_FLAG_ACTIVITY) > 0) && ("activity".equals(parser.getName()))) {
                    parseComponent(parser, packageName, ifw.activityMap);
                } else if (((cType & COMPONENT_FLAG_RECEIVER) > 0) && ("broadcast".equals(parser.getName()))) {
                    parseComponent(parser, packageName, ifw.receiverMap);
                } else if (((cType & COMPONENT_FLAG_SERVICE) > 0) && ("service".equals(parser.getName()))) {
                    parseComponent(parser, packageName, ifw.serviceMap);
                }
            }
        }
        in.close();
        return ifw;
    }

    /**
     * @param packageName 限定解析的App包名
     * @param map         packagename -> Set<className>
     */
    private static void parseComponent(XmlPullParser parser,
                                       @Nullable String packageName,
                                       @NonNull Map<String, Set<String>> map) {
        if (!"true".equals(parser.getAttributeValue(null, "block"))) {
            return;
        }
        int depth = parser.getDepth();
        int type;
        try {
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT &&
                    (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }
                if ("component-filter".equals(parser.getName())) {
                    String cName = parser.getAttributeValue(null, "name");
                    ComponentName c = ComponentName.unflattenFromString(cName);
                    if (c != null && (TextUtils.isEmpty(packageName)
                            || (TextUtils.equals(c.getPackageName(), packageName)))) {
                        Set<String> set = map.get(c.getPackageName());
                        if (set == null) {
                            set = new HashSet<>();
                            map.put(c.getPackageName(), set);
                        }
                        set.add(c.getClassName());
                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class IfwEntry {
        public Map<String, Set<String>> activityMap = new ArrayMap<>();
        public Map<String, Set<String>> receiverMap = new ArrayMap<>();
        public Map<String, Set<String>> serviceMap = new ArrayMap<>();
        public int status;

        public final static IfwEntry ROOT_ERROR, EMPTY;

        static {
            ROOT_ERROR = new IfwEntry();
            ROOT_ERROR.status = -1;

            EMPTY = new IfwEntry();
            EMPTY.status = 0;
        }

        public void clear() {
            if (activityMap != null) {
                activityMap.clear();
            }
            if (receiverMap != null) {
                receiverMap.clear();
            }
            if (serviceMap != null) {
                serviceMap.clear();
            }
        }

    }

}

Answer: public MultiSelectionUtils.Controller loadMultiController() {

<think>
The @Override annotation follows the ReceiverViewHolder constructor. The next method to implement is the abstract loadMultiController() from MultiSelectableViewHolder, so the next line should be the method signature that overrides it.
</think>
public MultiSelectionUtils.Controller loadMultiController() {