手机卫士1

MSG 08-02 添加黑名单布局
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.EditBlackNumberActivity">

    <TextView
        style="@style/TitleStyle"
        android:text="添加黑名单" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="电话号码" />

    <EditText
        android:id="@+id/et_search_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/edittext_input_selector"
        android:hint="请输入要查询的内容"
        android:inputType="number" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="拦截类型" />

    <RadioGroup
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="电话" />

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="短信" />

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="全部" />

    </RadioGroup>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="bottom"
        android:orientation="horizontal">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="保存" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消" />
    </LinearLayout>

</LinearLayout>

MSG 08-03 ButterKnife的使用

https://github.com/JakeWharton/butterknife

package cn.nubia.mobilesecurityguard.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import cn.nubia.mobilesecurityguard.R;

public class EditBlackNumberActivity extends AppCompatActivity {

    @BindView(R.id.et_search_content)
    EditText etSearchContent;
    @BindView(R.id.rb_phone)
    RadioButton rbPhone;
    @BindView(R.id.rb_sms)
    RadioButton rbSms;
    @BindView(R.id.rb_all)
    RadioButton rbAll;
    @BindView(R.id.bt_save)
    Button btSave;
    @BindView(R.id.bt_cancle)
    Button btCancle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_edit_black_number);
        ButterKnife.bind(this);

    }

    @OnClick({R.id.et_search_content, R.id.rb_phone, R.id.rb_sms, R.id.rb_all, R.id.bt_save, R.id.bt_cancle})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.et_search_content:
                Log.e("sunyang","点击搜索");
                break;
            case R.id.rb_phone:
                break;
            case R.id.rb_sms:
                break;
            case R.id.rb_all:
                break;
            case R.id.bt_save:
                break;
            case R.id.bt_cancle:
                break;
        }
    }
}

MSG 08-04 保存黑名单号码

   private void savePhoneNumber() {
        String number = etSearchContent.getText().toString().trim();
        if (TextUtils.isEmpty(number)) {
            Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
        }
        int radioButtonId = rgAll.getCheckedRadioButtonId();
        int mode = 0;
        switch (radioButtonId) {
            case R.id.rb_phone:
                mode = 0;
                break;
            case R.id.rb_sms:
                mode = 1;
                break;
            case R.id.rb_all:
                mode = 2;
                break;
        }
        //保存到数据库
        boolean isSuccess = dao.insert(number, mode);
        if (isSuccess) {
            Log.e("sunyang","已保存");
            finish();
        } else {

        }
    }
最新数据在数据库最下面

MSG 08-05 数据库逆序排列

    select * from blacknumber order by _id desc limit 0,20
    //逆序查询
    public ArrayList<BlackNumberInfo> queryPartDescNumberAndMode(int index) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select number, mode from blacknumber order by _id desc limit ?,20", new String[]{index + ""});
        ArrayList<BlackNumberInfo> list = new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                BlackNumberInfo info = new BlackNumberInfo();
                String number = cursor.getString(0);
                int mode = cursor.getInt(1);
                info.number = number;
                info.mode = mode;
                list.add(info);
            }
            cursor.close();
        }
        db.close();
        return list;
    }

MSG 08-06 保存黑名单并刷新页面(不重新加载数据库)

    private void addBlackNumber() {
        Intent intent = new Intent(BlackNumberActivity.this, EditBlackNumberActivity.class);
        startActivityForResult(intent, 0);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case 0:
                if (resultCode ==RESULT_OK) {
                    String number = data.getStringExtra("number");
                    int mode = data.getIntExtra("mode", 0);
                    //封装一个新对象
                    BlackNumberInfo info = new BlackNumberInfo();
                    info.number = number;
                    info.mode = mode;
                    list.add(info);
                    blackNumberAdapter.notifyDataSetChanged();
                }
                break;
        }
    }

    private void savePhoneNumber() {
        String number = etSearchContent.getText().toString().trim();
        if (TextUtils.isEmpty(number)) {
            Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
        }
        int radioButtonId = rgAll.getCheckedRadioButtonId();
        int mode = 0;
        switch (radioButtonId) {
            case R.id.rb_phone:
                mode = 0;
                break;
            case R.id.rb_sms:
                mode = 1;
                break;
            case R.id.rb_all:
                mode = 2;
                break;
        }
        //保存到数据库
        boolean isSuccess = dao.insert(number, mode);
        if (isSuccess) {
            Log.e("sunyang","已保存");
            Intent intent = new Intent();
            intent.putExtra("number", number);
            intent.putExtra("mode", mode);
            setResult(RESULT_OK,intent);
            finish();
        } else {

        }
    }
此时集合中数据在最后一条,需要将数据条件到第一条数据
list.add(0,info);

MSG 08-07 区分添加还是更新&更新黑名单数据回显

        lvAddNumber.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(getApplicationContext(), EditBlackNumberActivity.class);
                intent.putExtra("isUpdate", true);
                BlackNumberInfo info = list.get(position);
                intent.putExtra("number",info.number);
                intent.putExtra("mode", info.mode);
                startActivity(intent);
            }
        });

        //区分添加还是更新
        boolean isUpdate = getIntent().getBooleanExtra("isUpdate", false);
        if (isUpdate) {
            //修改标题栏
            tvTitle.setText("更新黑名单");
            //修改按钮文字
            btSave.setText("更新");
            //禁用文本框
            etSearchContent.setEnabled(false);
            //回显电话号码和拦截类型
            String number = getIntent().getStringExtra("number");
            etSearchContent.setText(number);
            int mode = getIntent().getIntExtra("mode", 0);
            switch (mode) {
                case 0:
                    rgAll.check(R.id.rb_phone);
                    break;
                case 1:
                    rgAll.check(R.id.rb_sms);
                    break;
                case 2:
                    rgAll.check(R.id.rb_all);
                    break;
            }
        }

MSG 08-08 更新黑名单并刷新页面(不重新加载数据库)

                if (resultCode == RESULT_OK) {
                    String number = data.getStringExtra("number");
                    int mode = data.getIntExtra("mode", 0);
                    //找到要更新的对象
                    updateInfo.mode = mode;
                    //刷新listview
                    blackNumberAdapter.notifyDataSetChanged();
                }

MSG 08-09 添加和更新黑名单流程小结
MSG 08-10 删除黑名单

        holder.ivDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //删除黑名单
                //1、从数据库删除
                dao.delete(info.number);
                //2、从集合删除
                list.remove(info);
                //3、刷新listview
                notifyDataSetChanged();
            }
        });

MSG 08-11 显示数据为空的布局

                            blackNumberAdapter = new BlackNumberAdapter(BlackNumberActivity.this, BlackNumberActivity.this.list);
                            lvAddNumber.setAdapter(blackNumberAdapter);
                            lvAddNumber.setEmptyView(ivEmpty);

MSG 08-12 添加骚扰拦截开关&创建服务

    private void startSrljService() {
        if (ServiceStatusUtils.isServiceRunning(getApplicationContext(),BlackNumberService.class)) {
            stopService(new Intent(getApplicationContext(),BlackNumberService.class));
        } else {
            startService(new Intent(getApplicationContext(),BlackNumberService.class));
        }
    }

MSG 08-13 黑名单拦截短信

package cn.nubia.mobilesecurityguard.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.telephony.SmsManager;

public class BlackNumberService extends Service {

    private SmsReceiver smsReceiver;

    public BlackNumberService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        //拦截短信

        //注册广播
        IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
        filter.setPriority(Integer.MAX_VALUE);
        smsReceiver = new SmsReceiver();
        registerReceiver(smsReceiver, filter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(smsReceiver);
        smsReceiver = null;
    }

    class SmsReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Object[] objs = (Object[]) intent.getExtras().get("pdus");
            for (Object obj : objs) {

            }
        }
    }
}

MSG 08-14 挂断电话&aidl文件配置

    <uses-permission android:name="android.permission.CALL_PHONE"/>

    private void endCall() {
        //反射获取系统服务的getService方法对象
        try {
            Method method = Class.forName("android.os.ServiceManager").getMethod("getService", String.class);
            //执行这个方法得到一个IBinder对象
            try {
                IBinder binder = (IBinder) method.invoke(null, TELEPHONY_SERVICE);
                //转换为具体的服务类
                ITelephony telephony = ITelephony.Stub.asInterface(binder);
                telephony.endCall();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

MSG 08-15 移除通话记录&内容观察者

    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>

    private void deleteCallLog(String number) {
        getContentResolver().delete(Uri.parse("content://call_log/calls"),"number=?",new String[]{number});
        //getContentResolver().delete(CallLog.Calls.CONTENT_URI,"number=?",new String[]{number});
    }

MSG 08-16 软件管理布局&自定义水平进度条

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    tools:showIn="@layout/activity_app_manager">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="内部存储:" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ProgressBar
            android:id="@+id/pb_progress"
            style="?android:attr/progressBarStyleHorizontal"
            android:progressDrawable="@drawable/custom_progress"
            android:layout_width="match_parent"
            android:progress="50"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/tv_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0M已用" />


        <TextView
            android:id="@+id/tv_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="0M可用" />

    </RelativeLayout>

</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/background"
        android:drawable="@drawable/progress_bg">
    </item>

    <item android:id="@+id/secondaryProgress">
        <scale
            android:drawable="@drawable/progress_secondary"
            android:scaleWidth="100%" />
    </item>

    <item android:id="@+id/progress">
        <scale
            android:drawable="@drawable/progress_progress"
            android:scaleWidth="100%" />
    </item>

</layer-list>

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="#E69191"></solid>

</shape>

MSG 08-17 自定义进度条组合控件

package cn.nubia.mobilesecurityguard.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;

import butterknife.BindView;
import butterknife.ButterKnife;
import cn.nubia.mobilesecurityguard.R;

/**
 * 自定义进度条的组合控件
 */
public class ProgressView extends LinearLayout {
    @BindView(R.id.tv_title)
    TextView tvTitle;
    @BindView(R.id.pb_progress)
    ProgressBar pbProgress;
    @BindView(R.id.tv_left)
    TextView tvLeft;
    @BindView(R.id.tv_right)
    TextView tvRight;

    public ProgressView(Context context) {
        this(context, null);
    }

    public ProgressView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public ProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context);
    }

    private void initView(Context context) {
        View view = View.inflate(context, R.layout.app_manager_progress, this);
        ButterKnife.bind(view);
    }

    public void setTitle(String title) {
        tvTitle.setText(title);
    }

    public void setleftText(String title) {
        tvLeft.setText(title);
    }

    public void setRightText(String title) {
        tvRight.setText(title);
    }

    public void setProgress(int progress) {
        pbProgress.setProgress(progress);
    }
}

    <cn.nubia.mobilesecurityguard.view.ProgressView
        android:id="@+id/left_pb"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </cn.nubia.mobilesecurityguard.view.ProgressView>

    <cn.nubia.mobilesecurityguard.view.ProgressView
        android:id="@+id/right_pb"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </cn.nubia.mobilesecurityguard.view.ProgressView>

MSG 08-18 软件管理初始化控件信息

    private void initSpaceInfo() {
        //内部存储
        long totalSpace = Environment.getDataDirectory().getTotalSpace();
        long freeSpace = Environment.getDataDirectory().getFreeSpace();
        long usedSpace = totalSpace - freeSpace;

        //Formatter将字节转换成带单位的字符串
        String use = Formatter.formatFileSize(this, usedSpace);
        inPb.setleftText(use);

        String free = Formatter.formatFileSize(this, freeSpace);
        inPb.setRightText(free);

        int progress = (int) (freeSpace * 100 / totalSpace);
        inPb.setProgress(progress);

        //外部存储
        long sdCardTotalSpace = Environment.getExternalStorageDirectory().getTotalSpace();
        long sdCardFreeSpace = Environment.getExternalStorageDirectory().getFreeSpace();
        long sdCardUsedSpace = sdCardTotalSpace - sdCardFreeSpace;

        //Formatter将字节转换成带单位的字符串
        String useSdCard = Formatter.formatFileSize(this, sdCardUsedSpace);
        outPb.setleftText(useSdCard);

        String freeSdCard = Formatter.formatFileSize(this, sdCardFreeSpace);
        outPb.setRightText(freeSdCard);

        int progressSdCard = (int) (sdCardFreeSpace * 100 / sdCardTotalSpace);
        outPb.setProgress(progressSdCard);
    }

MSG 08-19 获取所有已安装的app

package cn.nubia.mobilesecurityguard.bean;

import android.graphics.drawable.Drawable;

public class AppManagerInfo {

    public String packageName;
    public String name;
    public Drawable icon;
    public long size;

}

package cn.nubia.mobilesecurityguard.utils;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;

/**
 * 提供应用信息的工具类
 */
public class AppInfoProvider {

    //获取已安装的app信息
    public static ArrayList<AppManagerInfo> getInstalledApps(Context context) {
        PackageManager pm = context.getPackageManager();
        List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
        ArrayList<AppManagerInfo> list = new ArrayList<>();
        for (PackageInfo packageInfo : installedPackages) {
            AppManagerInfo info = new AppManagerInfo();
            String packageName = packageInfo.packageName;
            ApplicationInfo applicationInfo = packageInfo.applicationInfo;
            String name = applicationInfo.loadLabel(pm).toString();
            Drawable icon = applicationInfo.loadIcon(pm);
            //安装路径
            String sourceDir = applicationInfo.sourceDir;
            File file = new File(sourceDir);
            //文件大小
            long size = file.length();
            info.packageName = packageName;
            info.icon = icon;
            info.name = name;
            info.size = size;
            list.add(info);
        }
        return list;
    }
}

package cn.nubia.mobilesecurityguard.adapter;

import android.content.Context;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;

public class AppManagerAdapter extends BaseAdapter {

    private Context mContext;
    private ArrayList<AppManagerInfo> mList;

    public AppManagerAdapter(Context context, ArrayList list) {
        mContext = context;
        mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public AppManagerInfo getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View.inflate(mContext, R.layout.item_app_manager, null);
            holder = new ViewHolder();
            holder.ivIcon = convertView.findViewById(R.id.iv_icon);
            holder.tvName = convertView.findViewById(R.id.tv_name);
            holder.tvLocation = convertView.findViewById(R.id.tv_location);
            holder.tvSize = convertView.findViewById(R.id.tv_size);
            //将holder保存和当前布局绑定
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        AppManagerInfo info = getItem(position);
        holder.tvName.setText(info.name);
        holder.ivIcon.setImageDrawable(info.icon);
        String size = Formatter.formatFileSize(mContext, info.size);
        holder.tvSize.setText(size);
        return convertView;
    }

    static class ViewHolder {
        public ImageView ivIcon;
        public TextView tvName;
        public TextView tvLocation;
        public TextView tvSize;
    }
}

MSG 08-20 展示应用列表
MSG 08-21 软件安装位置&软件安装流程

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.nubia.mobilesecurityguard"
    android:installLocation="preferExternal">
三方软件:data/app
系统软件:system/app

MSG 08-22 判断软件安装位置

package cn.nubia.mobilesecurityguard.utils;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;

/**
 * 提供应用信息的工具类
 */
public class AppInfoProvider {

    //获取已安装的app信息
    public static ArrayList<AppManagerInfo> getInstalledApps(Context context) {
        PackageManager pm = context.getPackageManager();
        List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
        ArrayList<AppManagerInfo> list = new ArrayList<>();
        for (PackageInfo packageInfo : installedPackages) {
            AppManagerInfo info = new AppManagerInfo();
            String packageName = packageInfo.packageName;
            ApplicationInfo applicationInfo = packageInfo.applicationInfo;
            String name = applicationInfo.loadLabel(pm).toString();
            Drawable icon = applicationInfo.loadIcon(pm);
            //安装路径
            String sourceDir = applicationInfo.sourceDir;
            File file = new File(sourceDir);
            //文件大小
            long size = file.length();
            info.packageName = packageName;
            info.icon = icon;
            info.name = name;
            info.size = size;
            //判断软件安装位置
            int flags = applicationInfo.flags;
            if ((flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) > 0) {
                //当前app具备sdcard特性
                info.isSDCard = true;
            } else {
                //当前app具备内存特性
                info.isSDCard = false;
            }
            list.add(info);
        }
        return list;
    }
}

package cn.nubia.mobilesecurityguard.adapter;

import android.content.Context;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;

public class AppManagerAdapter extends BaseAdapter {

    private Context mContext;
    private ArrayList<AppManagerInfo> mList;

    public AppManagerAdapter(Context context, ArrayList list) {
        mContext = context;
        mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public AppManagerInfo getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View.inflate(mContext, R.layout.item_app_manager, null);
            holder = new ViewHolder();
            holder.ivIcon = convertView.findViewById(R.id.iv_icon);
            holder.tvName = convertView.findViewById(R.id.tv_name);
            holder.tvLocation = convertView.findViewById(R.id.tv_location);
            holder.tvSize = convertView.findViewById(R.id.tv_size);
            //将holder保存和当前布局绑定
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        AppManagerInfo info = getItem(position);
        holder.tvName.setText(info.name);
        holder.ivIcon.setImageDrawable(info.icon);
        String size = Formatter.formatFileSize(mContext, info.size);
        holder.tvSize.setText(size);
        boolean isSDCard = info.isSDCard;
        if (isSDCard) {
            holder.tvLocation.setText("sd卡");
        } else {
            holder.tvLocation.setText("手机内存");
        }
        return convertView;
    }

    static class ViewHolder {
        public ImageView ivIcon;
        public TextView tvName;
        public TextView tvLocation;
        public TextView tvSize;
    }
}

MSG 09-02 区分展示用户应用和系统应用

package cn.nubia.mobilesecurityguard.utils;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;

/**
 * 提供应用信息的工具类
 */
public class AppInfoProvider {

    //获取已安装的app信息
    public static ArrayList<AppManagerInfo> getInstalledApps(Context context) {
        PackageManager pm = context.getPackageManager();
        List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
        ArrayList<AppManagerInfo> list = new ArrayList<>();
        for (PackageInfo packageInfo : installedPackages) {
            AppManagerInfo info = new AppManagerInfo();
            String packageName = packageInfo.packageName;
            ApplicationInfo applicationInfo = packageInfo.applicationInfo;
            String name = applicationInfo.loadLabel(pm).toString();
            Drawable icon = applicationInfo.loadIcon(pm);
            //安装路径
            String sourceDir = applicationInfo.sourceDir;
            File file = new File(sourceDir);
            //文件大小
            long size = file.length();
            info.packageName = packageName;
            info.icon = icon;
            info.name = name;
            info.size = size;
            //判断软件安装位置
            int flags = applicationInfo.flags;
            if ((flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) > 0) {
                //当前app具备sdcard特性
                info.isSDCard = true;
            } else {
                //当前app具备内存特性
                info.isSDCard = false;
            }
            if ((flags & ApplicationInfo.FLAG_SYSTEM) > 0) {
                //当前app具备系统app
                info.isUserApp = false;
            } else {
                //当前app具备用户app
                info.isUserApp = true;
            }
            list.add(info);
        }
        return list;
    }
}

获取系统app和用户app
                sysList = new ArrayList();
                appList = new ArrayList();
                for (int i = 0; i < installedApps.size(); i++) {
                    if (installedApps.get(i).isUserApp) {
                        appList.add(installedApps.get(i));
                    } else {
                        sysList.add(installedApps.get(i));
                    }
                }
                //清空集合
                installedApps.clear();
                installedApps.addAll(appList);
                installedApps.addAll(sysList);

MSG 09-03 给集合添加标题栏对象

package cn.nubia.mobilesecurityguard.bean;

import android.graphics.drawable.Drawable;

public class AppManagerInfo {

    public String packageName;
    public String name;
    public Drawable icon;
    public long size;
    public boolean isSDCard;
    public boolean isUserApp;
    public boolean isTitle; //标记当前是否是标题栏
    public String title;
}

package cn.nubia.mobilesecurityguard.adapter;

import android.content.Context;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;

public class AppManagerAdapter extends BaseAdapter {

    private Context mContext;
    private ArrayList<AppManagerInfo> mList;

    public AppManagerAdapter(Context context, ArrayList list) {
        mContext = context;
        mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public AppManagerInfo getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View.inflate(mContext, R.layout.item_app_manager, null);
            holder = new ViewHolder();
            holder.ivIcon = convertView.findViewById(R.id.iv_icon);
            holder.tvName = convertView.findViewById(R.id.tv_name);
            holder.tvLocation = convertView.findViewById(R.id.tv_location);
            holder.tvSize = convertView.findViewById(R.id.tv_size);
            //将holder保存和当前布局绑定
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        AppManagerInfo info = getItem(position);
        if(!info.isTitle) {
            holder.tvName.setText(info.name);
            holder.ivIcon.setImageDrawable(info.icon);
            String size = Formatter.formatFileSize(mContext, info.size);
            holder.tvSize.setText(size);
            boolean isSDCard = info.isSDCard;
            if (isSDCard) {
                holder.tvLocation.setText("sd卡");
            } else {
                holder.tvLocation.setText("手机内存");
            }
        } else {
            holder.tvName.setText(info.title);
        }
        return convertView;
    }

    static class ViewHolder {
        public ImageView ivIcon;
        public TextView tvName;
        public TextView tvLocation;
        public TextView tvSize;
    }
}

package cn.nubia.mobilesecurityguard.activity;

import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.text.format.Formatter;
import android.widget.ListView;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.ButterKnife;
import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.adapter.AppManagerAdapter;
import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;
import cn.nubia.mobilesecurityguard.utils.AppInfoProvider;
import cn.nubia.mobilesecurityguard.view.ProgressView;

public class AppManagerActivity extends AppCompatActivity {

    @BindView(R.id.left_pb)
    ProgressView inPb;
    @BindView(R.id.right_pb)
    ProgressView outPb;
    @BindView(R.id.lv_app_manager)
    ListView lvAppManager;
    private ArrayList sysList;
    private ArrayList<AppManagerInfo> installedApps;
    private ArrayList appList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_manager);
        ButterKnife.bind(this);

        inPb.setTitle("内部存储:");
        outPb.setTitle("外部存储:");

        initData();
    }

    private void initData() {
        initSpaceInfo();
        new Thread() {
            @Override
            public void run() {
                //super.run();
                installedApps = AppInfoProvider.getInstalledApps(AppManagerActivity.this);
                sysList = new ArrayList();
                appList = new ArrayList();
                for (int i = 0; i < installedApps.size(); i++) {
                    if (installedApps.get(i).isUserApp) {
                        appList.add(installedApps.get(i));
                    } else {
                        sysList.add(installedApps.get(i));
                    }
                }
                //清空集合
                installedApps.clear();
                //添加用户应用的标题栏
                AppManagerInfo infoApp = new AppManagerInfo();
                infoApp.isTitle = true;
                infoApp.title = "用户应用userApp";
                installedApps.add(infoApp);
                installedApps.addAll(appList);
                //添加系统应用的标题栏
                AppManagerInfo infoSys = new AppManagerInfo();
                infoSys.isTitle = true;
                infoSys.title = "系统应用sysApp";
                installedApps.add(infoSys);
                installedApps.addAll(sysList);

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        AppManagerAdapter adapter = new AppManagerAdapter(AppManagerActivity.this, installedApps);
                        lvAppManager.setAdapter(adapter);
                    }
                });

            }
        }.start();
    }

    private void initSpaceInfo() {
        //内部存储
        long totalSpace = Environment.getDataDirectory().getTotalSpace();
        long freeSpace = Environment.getDataDirectory().getFreeSpace();
        long usedSpace = totalSpace - freeSpace;

        //Formatter将字节转换成带单位的字符串
        String use = Formatter.formatFileSize(this, usedSpace);
        inPb.setleftText(use);

        String free = Formatter.formatFileSize(this, freeSpace);
        inPb.setRightText(free);

        int progress = (int) (freeSpace * 100 / totalSpace);
        inPb.setProgress(progress);

        //外部存储
        long sdCardTotalSpace = Environment.getExternalStorageDirectory().getTotalSpace();
        long sdCardFreeSpace = Environment.getExternalStorageDirectory().getFreeSpace();
        long sdCardUsedSpace = sdCardTotalSpace - sdCardFreeSpace;

        //Formatter将字节转换成带单位的字符串
        String useSdCard = Formatter.formatFileSize(this, sdCardUsedSpace);
        outPb.setleftText(useSdCard);

        String freeSdCard = Formatter.formatFileSize(this, sdCardFreeSpace);
        outPb.setRightText(freeSdCard);

        int progressSdCard = (int) (sdCardFreeSpace * 100 / sdCardTotalSpace);
        outPb.setProgress(progressSdCard);
    }
}

MSG 09-04 ListView展示多种布局类型

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="20dp"
        android:text="标题栏" />

</LinearLayout>

多种布局:
package cn.nubia.mobilesecurityguard.adapter;

import android.content.Context;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;

public class AppManagerAdapter extends BaseAdapter {

    private Context mContext;
    private ArrayList<AppManagerInfo> mList;

    public AppManagerAdapter(Context context, ArrayList list) {
        mContext = context;
        mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public AppManagerInfo getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    //布局的类型
    @Override
    public int getItemViewType(int position) {
        AppManagerInfo info = getItem(position);
        if (info.isTitle) {
            return 0;
        } else {
            return 1;
        }
        //return super.getItemViewType(position);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        int itemViewType = getItemViewType(position);
        switch (itemViewType) {
            case 0:
                TitleHolder titleHolder = null;
                if (convertView == null) {
                    convertView = View.inflate(mContext, R.layout.item_app_title, null);
                    titleHolder = new TitleHolder();
                    titleHolder.tvTitle = convertView.findViewById(R.id.tv_title);
                    //将holder保存和当前布局绑定
                    convertView.setTag(titleHolder);
                } else {
                    titleHolder = (TitleHolder) convertView.getTag();
                }
                AppManagerInfo titleInfo = getItem(position);
                if (position == 0) {
                    titleHolder.tvTitle.setText(titleInfo.title+"("+"10)");
                } else {
                    titleHolder.tvTitle.setText(titleInfo.title+"("+"20)");
                }
                break;
            case 1:
                ViewHolder holder = null;
                if (convertView == null) {
                    convertView = View.inflate(mContext, R.layout.item_app_manager, null);
                    holder = new ViewHolder();
                    holder.ivIcon = convertView.findViewById(R.id.iv_icon);
                    holder.tvName = convertView.findViewById(R.id.tv_name);
                    holder.tvLocation = convertView.findViewById(R.id.tv_location);
                    holder.tvSize = convertView.findViewById(R.id.tv_size);
                    //将holder保存和当前布局绑定
                    convertView.setTag(holder);
                } else {
                    holder = (ViewHolder) convertView.getTag();
                }

                AppManagerInfo info = getItem(position);
                    holder.tvName.setText(info.name);
                    holder.ivIcon.setImageDrawable(info.icon);
                    String size = Formatter.formatFileSize(mContext, info.size);
                    holder.tvSize.setText(size);
                    boolean isSDCard = info.isSDCard;
                    if (isSDCard) {
                        holder.tvLocation.setText("sd卡");
                    } else {
                        holder.tvLocation.setText("手机内存");
                    }
                break;
        }
        return convertView;
    }

    static class ViewHolder {
        public ImageView ivIcon;
        public TextView tvName;
        public TextView tvLocation;
        public TextView tvSize;
    }

    static class TitleHolder {
        public TextView tvTitle;
    }
}

MSG 09-05 常驻浮窗开发

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/lv_app_manager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </ListView>

        <TextView
            android:id="@+id/tv_titles"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:background="#f00"
            android:text="标题栏" />

    </FrameLayout>

listview滑动监听:
        lvAppManager.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                if (sysList != null && appList != null) {
                    if (firstVisibleItem >= appList.size() + 1) {
                        tvTitle.setText("系统应用sysApp(" + sysList.size() + ")");
                    } else {
                        tvTitle.setText("用户应用userApp(" + appList.size() + ")");
                    }
                }
            }
        });

MSG 09-06 PopupWindow的使用

popupwindow布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:drawableTop="@drawable/ic_uninstall"
        android:drawablePadding="3dp"
        android:text="卸载" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:drawableTop="@drawable/ic_open"
        android:drawablePadding="3dp"
        android:text="打开" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:drawableTop="@drawable/ic_share"
        android:drawablePadding="3dp"
        android:text="分享" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:drawableTop="@drawable/ic_info"
        android:drawablePadding="3dp"
        android:text="信息" />

</LinearLayout>

加载布局:
        lvAppManager.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                showPopupWindow(view);
            }
        });

    private void showPopupWindow(View itemView) {
        View view = View.inflate(this, R.layout.popup_appinfo, null);
        //第4个参数:是否有焦点
        PopupWindow mPopup = new PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        //设置一个背景,透明色,只有设置背景,点击返回键和窗口外侧,窗口才会消失
        mPopup.setBackgroundDrawable(new ColorDrawable());
        //mPopup.showAtLocation(itemView,0,0);
        mPopup.showAsDropDown(itemView, 80, -itemView.getHeight());
    }

MSG 09-07 PopupWindow添加动画

popup动画风格:
    <!--归属地动画-->
    <style name="PopupWindowAnimStyle">
        <item name="android:windowEnterAnimation">@anim/popup_enter</item>
        <item name="android:windowExitAnimation">@anim/popup__exit</item>
    </style>

popup进入动画:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">

    <alpha
        android:duration="300"
        android:fromAlpha="1.0"
        android:toAlpha="0.0" />

    <translate
        android:duration="300"
        android:fromXDelta="0"
        android:interpolator="@android:anim/overshoot_interpolator"
        android:toXDelta="200%" />
</set>
popup退出动画:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">

    <alpha
        android:duration="150"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />

    <translate
        android:duration="300"
        android:fromXDelta="200%"
        android:interpolator="@android:anim/anticipate_interpolator"
        android:toXDelta="0" />
</set>

设置动画:
    private void showPopupWindow(View itemView) {
        View view = View.inflate(this, R.layout.popup_appinfo, null);
        //第4个参数:是否有焦点
        PopupWindow mPopup = new PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        //设置一个背景,透明色,只有设置背景,点击返回键和窗口外侧,窗口才会消失
        mPopup.setBackgroundDrawable(new ColorDrawable());
        //设置动画样式
        mPopup.setAnimationStyle(R.style.PopupWindowAnimStyle);
        //mPopup.showAtLocation(itemView,0,0);
        mPopup.showAsDropDown(itemView, 80, -itemView.getHeight());
    }

MSG 09-08 PopupWindow点击事件设置

    private void showPopupWindow(View itemView) {
        View view = View.inflate(this, R.layout.popup_appinfo, null);
        TextView tvUninstall = view.findViewById(R.id.tv_uninstall);
        TextView tvOpen = view.findViewById(R.id.tv_open);
        TextView tvShare = view.findViewById(R.id.tv_share);
        TextView tvInfo = view.findViewById(R.id.tv_info);
        tvUninstall.setOnClickListener(this);
        tvOpen.setOnClickListener(this);
        tvShare.setOnClickListener(this);
        tvInfo.setOnClickListener(this);
        //第4个参数:是否有焦点
        mPopup = new PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        //设置一个背景,透明色,只有设置背景,点击返回键和窗口外侧,窗口才会消失
        mPopup.setBackgroundDrawable(new ColorDrawable());
        //设置动画样式
        mPopup.setAnimationStyle(R.style.PopupWindowAnimStyle);
        //mPopup.showAtLocation(itemView,0,0);
        mPopup.showAsDropDown(itemView, 200, -itemView.getHeight());
    }

    @Override
    public void onClick(View v) {
        mPopup.dismiss();
        switch (v.getId()) {
            case R.id.tv_uninstall:
                Log.e("sunyang","卸载");
                break;
            case R.id.tv_open:
                break;
            case R.id.tv_share:
                break;
            case R.id.tv_info:
                break;
        }
    }

MSG 09-09 卸载应用

                String packageName = currInfo.packageName;
                Uri parse = Uri.parse("package:" + packageName);
                Intent intent = new Intent(Intent.ACTION_DELETE, parse);
                startActivityForResult(intent,0);
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        initData();
    }

MSG 09-10 打开应用

                PackageManager packageManager = getPackageManager();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    Intent launchIntentForPackage = packageManager.getLeanbackLaunchIntentForPackage(currInfo.packageName);
                     if (launchIntentForPackage != null) {
                        startActivity(launchIntentForPackage);
                    }
                }

MSG 09-11 分享功能

                Intent share = new Intent(Intent.ACTION_SEND);
                share.setType("text/plain");
                share.putExtra(Intent.EXTRA_TEXT,"你好,我叫算。。");
                startActivity(share);

MSG 09-12 跳到应用信息页

                Intent info = new Intent();
                info.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                Uri uri = Uri.fromParts("package", currInfo.packageName, null);
                info.setData(uri);
                startActivity(info);

MSG 10-02 ExpandableListView的使用

    <ExpandableListView
        android:id="@+id/elv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:groupIndicator="@null">

    </ExpandableListView>

package cn.nubia.mobilesecurityguard.activity;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;

import butterknife.BindView;
import butterknife.ButterKnife;
import cn.nubia.mobilesecurityguard.R;

public class CommonNumberActivity extends AppCompatActivity {

    @BindView(R.id.elv_list)
    ExpandableListView elvList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common_number);
        ButterKnife.bind(this);

        CommonNumberAdapter adapter = new CommonNumberAdapter();
        elvList.setAdapter(adapter);
    }


    class CommonNumberAdapter extends BaseExpandableListAdapter {

        //返回组的个数
        @Override
        public int getGroupCount() {
            return 5;
        }

        //返回某组孩子的个数
        @Override
        public int getChildrenCount(int groupPosition) {
            return 3;
        }

        //getItem
        @Override
        public Object getGroup(int groupPosition) {
            return null;
        }

        //返回孩子对象
        @Override
        public Object getChild(int groupPosition, int childPosition) {
            return null;
        }

        @Override
        public long getGroupId(int groupPosition) {
            return 0;
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        //是否有固定id
        @Override
        public boolean hasStableIds() {
            return false;
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            TextView textView = new TextView(CommonNumberActivity.this);
            textView.setText("第" + groupPosition + "组");
            textView.setBackgroundColor(Color.GRAY);
            textView.setPadding(10, 10, 10, 10);
            textView.setTextSize(18);
            return textView;
        }

        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
            TextView textView = new TextView(CommonNumberActivity.this);
            textView.setText("第" + groupPosition + "组" + "第" + childPosition + "项");
            textView.setBackgroundColor(Color.GREEN);
            textView.setPadding(10, 10, 10, 10);
            textView.setTextSize(14);
            return textView;
        }

        //孩子是否可以点击
        @Override
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            return false;
        }
    }
}

MSG 10-03 常用号码数据库封装

package cn.nubia.mobilesecurityguard.db.dao;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.util.ArrayList;

public class CommonNumberDao {

    public static ArrayList<GroupInfo> getCommonNumbers(Context context) {
        //读取数据库文件
        String dbFilePath = context.getFilesDir().getAbsolutePath();
        //以只读方式打开数据库,参1:数据库文件的本地路径
        SQLiteDatabase database = SQLiteDatabase.openDatabase(dbFilePath + "/commonnum.db", null, SQLiteDatabase.OPEN_READONLY);
        //需要将资产目录下的数据库文件拷贝到本地文件路径下(一般在闪屏页面操作)copyDb()
        //data/data/packagename/files/下

        Cursor cursor = database.query("classlist", new String[]{"name", "idx"}, null, null, null, null, null);
        ArrayList<GroupInfo> mList = new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                GroupInfo groupInfo = new GroupInfo();
                groupInfo.name = cursor.getString(0);
                groupInfo.idx = cursor.getString(1);
                groupInfo.children = getChildList(groupInfo.idx, database);
                mList.add(groupInfo);
            }
            cursor.close();
        }
        database.close();
        return mList;
    }

    private static ArrayList<ChildInfo> getChildList(String idx, SQLiteDatabase database) {
        Cursor cursor = database.query("table" + idx, new String[]{"number", "name"}, null, null, null, null, null);
        ArrayList<ChildInfo> mList = new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                ChildInfo childInfo = new ChildInfo();
                childInfo.number = cursor.getString(0);
                childInfo.name = cursor.getString(1);
                mList.add(childInfo);
            }
            cursor.close();
        }
        //database.close(); while循环还没结束
        return mList;
    }

   public static class GroupInfo {
        public String name;
        public String idx;
        public ArrayList<ChildInfo> children;
    }

   public static class ChildInfo {
        public String number;
        public String name;
    }
}

MSG 10-04 常用号码数据展示及点击处理

package cn.nubia.mobilesecurityguard.activity;

import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.ButterKnife;
import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.db.dao.CommonNumberDao;

public class CommonNumberActivity extends AppCompatActivity {

    @BindView(R.id.elv_list)
    ExpandableListView elvList;
    private ArrayList<CommonNumberDao.GroupInfo> commonNumbers;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common_number);
        ButterKnife.bind(this);

        initData();
        CommonNumberAdapter adapter = new CommonNumberAdapter();
        elvList.setAdapter(adapter);
        elvList.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
                //跳转到对应打电话界面
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:" + commonNumbers.get(groupPosition).children.get(childPosition).number));
                startActivity(intent);
                return false;
            }
        });
    }

    private void initData() {
        commonNumbers = CommonNumberDao.getCommonNumbers(this);

    }


    class CommonNumberAdapter extends BaseExpandableListAdapter {

        //返回组的个数
        @Override
        public int getGroupCount() {
            return commonNumbers.size();
        }

        //返回某组孩子的个数
        @Override
        public int getChildrenCount(int groupPosition) {
            return commonNumbers.get(groupPosition).children.size();
        }

        //getItem
        @Override
        public CommonNumberDao.GroupInfo getGroup(int groupPosition) {
            return commonNumbers.get(groupPosition);
        }

        //返回孩子对象
        @Override
        public CommonNumberDao.ChildInfo getChild(int groupPosition, int childPosition) {
            return commonNumbers.get(groupPosition).children.get(childPosition);
        }

        @Override
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        //是否有固定id
        @Override
        public boolean hasStableIds() {
            return false;
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            TextView textView = new TextView(CommonNumberActivity.this);
            CommonNumberDao.GroupInfo group = getGroup(groupPosition);
            textView.setText(group.name);
            textView.setBackgroundColor(Color.GRAY);
            textView.setPadding(10, 10, 10, 10);
            textView.setTextSize(18);
            return textView;
        }

        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
            TextView textView = new TextView(CommonNumberActivity.this);
            CommonNumberDao.ChildInfo child = getChild(groupPosition, childPosition);
            textView.setText(child.name + "\n" + child.number);
            textView.setBackgroundColor(Color.GREEN);
            textView.setPadding(10, 10, 10, 10);
            textView.setTextSize(14);
            return textView;
        }

        //孩子是否可以点击
        @Override
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            return true;
        }
    }
}

MSG 10-05 读取短信数据

package cn.nubia.mobilesecurityguard.utils;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.Telephony;
import android.util.Log;

import java.util.ArrayList;

/**
 * 短信备份和还原
 */
public class SmsUtils {

    private static Cursor cursor;
    private static ArrayList<SmsInfo> mList;

    //短信备份
    public static void smsBackup(Context context) {
        //1、读取系统的短信数据库,data/data/android.provider.tetephony/databases/mmssms.db
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            cursor = context.getContentResolver().query(Telephony.Sms.CONTENT_URI, new String[]{"address", "date", "read", "type", "body"}, null, null, null);
        } else {
            cursor = context.getContentResolver().query(Uri.parse("content://sms"), new String[]{"address", "date", "read", "type", "body"}, null, null, null);
        }
        //3、权限
        //<uses-permission android:name="android.permission.READ_SMS"/>
        //<uses-permission android:name="android.permission.WRITE_SMS"/>
        mList = new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                SmsInfo smsInfo = new SmsInfo();
                //smsInfo.address = cursor.getString(0);
                smsInfo.address = cursor.getString(cursor.getColumnIndex("address"));
                smsInfo.date = cursor.getString(cursor.getColumnIndex("date"));
                smsInfo.read = cursor.getString(cursor.getColumnIndex("read"));
                smsInfo.type = cursor.getString(cursor.getColumnIndex("type"));
                smsInfo.body = cursor.getString(cursor.getColumnIndex("body"));
                mList.add(smsInfo);
            }
            cursor.close();
        }
        Log.e("sunyang", "短信:" + mList);
        //2、将短信内容保存在本地,将集合序列化 Gson


    }


    public static class SmsInfo {
        public String address;
        public String date;
        public String read;
        public String type;
        public String body;

        @Override
        public String toString() {
            return "SmsInfo{" +
                    "address='" + address + '\'' +
                    ", date='" + date + '\'' +
                    ", read='" + read + '\'' +
                    ", type='" + type + '\'' +
                    ", body='" + body + '\'' +
                    '}';
        }
    }
}

MSG 10-06 Gson使用

        //2、将短信内容保存在本地,将集合序列化 Gson
        Gson gson = new Gson();
        String json = gson.toJson(mList);
        Log.e("sunyang", "短信序列化:" + json);

MSG 10-07 将短信写入本地文件

        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(json.getBytes());
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        备份到:
                File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/sms.backup");
                SmsUtils.smsBackup(this, file);

MSG 10-08 备份进度展示

1、创建进度条
    private void smsBackup() {
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return;
        }

        final ProgressDialog dialog = new ProgressDialog(this);
        dialog.setTitle("正在备份");
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.show();
        new Thread() {
            @Override
            public void run() {
                //super.run();
                File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/sms.backup");
                SmsUtils.smsBackup(CommonToolsActivity.this, file, dialog);
                dialog.dismiss();
            }
        }.start();
    }
2、进度计算
package cn.nubia.mobilesecurityguard.utils;

import android.app.ProgressDialog;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.Telephony;
import android.util.Log;

import com.google.gson.Gson;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

/**
 * 短信备份和还原
 */
public class SmsUtils {

    private static Cursor cursor;
    private static ArrayList<SmsInfo> mList;

    //短信备份
    public static void smsBackup(Context context, File file, ProgressDialog dialog) {
        //1、读取系统的短信数据库,data/data/android.provider.tetephony/databases/mmssms.db
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            cursor = context.getContentResolver().query(Telephony.Sms.CONTENT_URI, new String[]{"address", "date", "read", "type", "body"}, null, null, null);
        } else {
            cursor = context.getContentResolver().query(Uri.parse("content://sms"), new String[]{"address", "date", "read", "type", "body"}, null, null, null);
        }

        int totalCount = cursor.getCount();
        dialog.setMax(totalCount);
        //3、权限
        //<uses-permission android:name="android.permission.READ_SMS"/>
        //<uses-permission android:name="android.permission.WRITE_SMS"/>
        mList = new ArrayList<>();
        if (cursor != null) {
            int progress = 0;//当前备份条数
            while (cursor.moveToNext()) {
                SmsInfo smsInfo = new SmsInfo();
                //smsInfo.address = cursor.getString(0);
                smsInfo.address = cursor.getString(cursor.getColumnIndex("address"));
                smsInfo.date = cursor.getString(cursor.getColumnIndex("date"));
                smsInfo.read = cursor.getString(cursor.getColumnIndex("read"));
                smsInfo.type = cursor.getString(cursor.getColumnIndex("type"));
                smsInfo.body = cursor.getString(cursor.getColumnIndex("body"));
                mList.add(smsInfo);
                progress++;
                //int percent = progress * 100 / totalCount;
                //dialog.setProgress(percent);
                dialog.setProgress(progress);
            }
            cursor.close();
        }
        Log.e("sunyang", "短信:" + mList);
        //2、将短信内容保存在本地,将集合序列化 Gson
        Gson gson = new Gson();
        String json = gson.toJson(mList);
        Log.e("sunyang", "短信序列化:" + json);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(json.getBytes());
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static class SmsInfo {
        public String address;
        public String date;
        public String read;
        public String type;
        public String body;

        @Override
        public String toString() {
            return "SmsInfo{" +
                    "address='" + address + '\'' +
                    ", date='" + date + '\'' +
                    ", read='" + read + '\'' +
                    ", type='" + type + '\'' +
                    ", body='" + body + '\'' +
                    '}';
        }
    }
}

备注:6.0以上增加动态权限

MSG 10-09 项目耦合重引起的问题

回调实现解耦:
    private void smsBackup() {
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return;
        }

        dialog = new ProgressDialog(this);
        dialog.setTitle("正在备份");
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.show();
        new Thread() {
            @Override
            public void run() {
                //super.run();
                File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/sms.backup");
                SmsUtils.smsBackup(CommonToolsActivity.this, file, new SmsCallback());
                dialog.dismiss();
            }
        }.start();
    }

    //1、声明回调接口
    public interface OnSmsCallback {
        //获取短信个数
        void onGetTotalCount(int totalCount);
        //当前备份数量
        void onGetCurrentProgress(int progress);
    }

    //2、实现回调接口
    class SmsCallback implements OnSmsCallback {

        @Override
        public void onGetTotalCount(int totalCount) {
            dialog.setMax(totalCount);
        }

        @Override
        public void onGetCurrentProgress(int progress) {
            dialog.setProgress(progress);
        }
    }

MSG 10-10 回调的使用
MSG 10-11 回调方法小结
MSG 10-12 将文件反序列化为对象

    public static void smsRestore(Context context, File file, CommonToolsActivity.OnSmsCallback callback) {
        //1、从本地文件读取短信
        Gson gson = new Gson();
        //2、输入流
        try {
            Type type = new TypeToken<ArrayList<SmsInfo>>() {
            }.getType();
            ArrayList<SmsInfo> list = gson.fromJson(new FileReader(file), type);
            callback.onGetTotalCount(list.size());
            //3、集合
            ContentResolver resolver = context.getContentResolver();
            int progress = 0;
            for (SmsInfo info : list) {
                ContentValues values = new ContentValues();
                values.put("address", info.address);
                values.put("date", info.date);
                values.put("read", info.read);
                values.put("type", info.type);
                values.put("body", info.body);
                //4、将数据插入数据库
                resolver.insert(Uri.parse("content://sms"), values);
                progress++;
                callback.onGetCurrentProgress(progress);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

MSG 10-13 短信还原&插入短信数据库

备份和还原的完整代码:
package cn.nubia.mobilesecurityguard.utils;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.Telephony;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.activity.CommonToolsActivity;

/**
 * 短信备份和还原
 */
public class SmsUtils {

    private static Cursor cursor;
    private static ArrayList<SmsInfo> mList;

    //短信备份
    public static void smsBackup(Context context, File file, CommonToolsActivity.OnSmsCallback callback) {
        //1、读取系统的短信数据库,data/data/android.provider.tetephony/databases/mmssms.db
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            cursor = context.getContentResolver().query(Telephony.Sms.CONTENT_URI, new String[]{"address", "date", "read", "type", "body"}, null, null, null);
        } else {
            cursor = context.getContentResolver().query(Uri.parse("content://sms"), new String[]{"address", "date", "read", "type", "body"}, null, null, null);
        }

        int totalCount = cursor.getCount();
        //dialog.setMax(totalCount);
        callback.onGetTotalCount(totalCount);
        //3、权限
        //<uses-permission android:name="android.permission.READ_SMS"/>
        //<uses-permission android:name="android.permission.WRITE_SMS"/>
        mList = new ArrayList<>();
        if (cursor != null) {
            int progress = 0;//当前备份条数
            while (cursor.moveToNext()) {
                SmsInfo smsInfo = new SmsInfo();
                //smsInfo.address = cursor.getString(0);
                smsInfo.address = cursor.getString(cursor.getColumnIndex("address"));
                smsInfo.date = cursor.getString(cursor.getColumnIndex("date"));
                smsInfo.read = cursor.getString(cursor.getColumnIndex("read"));
                smsInfo.type = cursor.getString(cursor.getColumnIndex("type"));
                smsInfo.body = cursor.getString(cursor.getColumnIndex("body"));
                mList.add(smsInfo);
                progress++;
                //int percent = progress * 100 / totalCount;
                //dialog.setProgress(percent);
                //dialog.setProgress(progress);
                callback.onGetCurrentProgress(progress);
            }
            cursor.close();
        }
        Log.e("sunyang", "短信:" + mList);
        //2、将短信内容保存在本地,将集合序列化 Gson
        Gson gson = new Gson();
        String json = gson.toJson(mList);
        Log.e("sunyang", "短信序列化:" + json);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(json.getBytes());
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void smsRestore(Context context, File file, CommonToolsActivity.OnSmsCallback callback) {
        //1、从本地文件读取短信
        Gson gson = new Gson();
        //2、输入流
        try {
            Type type = new TypeToken<ArrayList<SmsInfo>>() {
            }.getType();
            ArrayList<SmsInfo> list = gson.fromJson(new FileReader(file), type);
            callback.onGetTotalCount(list.size());
            //3、集合
            ContentResolver resolver = context.getContentResolver();
            int progress = 0;
            for (SmsInfo info : list) {
                Cursor cursor = resolver.query(Uri.parse("content://sms"), null, "address=? and date=? and read=? and type=? and body=?", new String[]{info.address, info.date, info.read, info.type, info.body}, null);
                if (cursor != null) {
                    if (cursor.moveToNext()) {
                        continue;
                    }
                }
                ContentValues values = new ContentValues();
                values.put("address", info.address);
                values.put("date", info.date);
                values.put("read", info.read);
                values.put("type", info.type);
                values.put("body", info.body);
                //4、将数据插入数据库
                resolver.insert(Uri.parse("content://sms"), values);
                progress++;
                callback.onGetCurrentProgress(progress);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }


    public static class SmsInfo {
        public String address;
        public String date;
        public String read;
        public String type;
        public String body;

        @Override
        public String toString() {
            return "SmsInfo{" +
                    "address='" + address + '\'' +
                    ", date='" + date + '\'' +
                    ", read='" + read + '\'' +
                    ", type='" + type + '\'' +
                    ", body='" + body + '\'' +
                    '}';
        }
    }
}

备份和还原代码:
package cn.nubia.mobilesecurityguard.activity;

import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.File;
import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.utils.SmsUtils;
import cn.nubia.mobilesecurityguard.view.SettingItemView;

public class CommonToolsActivity extends AppCompatActivity implements View.OnClickListener {

    private SettingItemView click1;
    private SettingItemView click2;
    private SettingItemView click3;
    private SettingItemView click4;
    private SettingItemView click5;
    private SettingItemView click6;
    private ProgressDialog dialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common_tools);

        initView();
        initData();
    }

    private void initData() {

    }

    private void initView() {
        click1 = findViewById(R.id.click1);
        click2 = findViewById(R.id.click2);
        click3 = findViewById(R.id.click3);
        click4 = findViewById(R.id.click4);
        click5 = findViewById(R.id.click5);
        click6 = findViewById(R.id.click6);
        click1.setOnClickListener(this);
        click2.setOnClickListener(this);
        click3.setOnClickListener(this);
        click4.setOnClickListener(this);
        click5.setOnClickListener(this);
        click6.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.click1:
                Log.e("gordon", "号码归属地查询");
                Intent intent1 = new Intent(this, AddressActivity.class);
                startActivity(intent1);
                break;
            case R.id.click2:
                Log.e("gordon", "常用号码查询");
                Intent intent2 = new Intent(this, CommonNumberActivity.class);
                startActivity(intent2);
                break;
            case R.id.click3:
                Log.e("gordon", "短信备份");
                smsBackup();
                break;
            case R.id.click4:
                Log.e("gordon", "短信还原");
                smsRestore();
                break;
            case R.id.click5:
                Log.e("gordon", "程序锁管理");
                Intent intent5 = new Intent(this, CommonToolsActivity.class);
                startActivity(intent5);
                break;
            case R.id.click6:
                Log.e("gordon", "开启程序锁服务");
                break;
        }
    }

    private void smsRestore() {
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return;
        }
        dialog = new ProgressDialog(this);
        dialog.setTitle("正在还原");
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.show();
        new Thread() {
            @Override
            public void run() {
                //super.run();
                File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/sms.backup");
                SmsUtils.smsRestore(CommonToolsActivity.this, file, new SmsCallback());
                dialog.dismiss();
            }
        }.start();
    }

    private void smsBackup() {
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return;
        }

        dialog = new ProgressDialog(this);
        dialog.setTitle("正在备份");
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.show();
        new Thread() {
            @Override
            public void run() {
                //super.run();
                File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/sms.backup");
                SmsUtils.smsBackup(CommonToolsActivity.this, file, new SmsCallback());
                dialog.dismiss();
            }
        }.start();
    }

    //1、声明回调接口
    public interface OnSmsCallback {
        //获取短信个数
        void onGetTotalCount(int totalCount);
        //当前备份数量
        void onGetCurrentProgress(int progress);
    }

    //2、实现回调接口
    class SmsCallback implements OnSmsCallback {

        @Override
        public void onGetTotalCount(int totalCount) {
            dialog.setMax(totalCount);
        }

        @Override
        public void onGetCurrentProgress(int progress) {
            dialog.setProgress(progress);
        }
    }
}

MSG 10-14 进程数据信息获取

package cn.nubia.mobilesecurityguard.utils;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;

import java.util.HashSet;
import java.util.List;

/**
 * <service
 * android:name=".service.BlackNumberService"
 * android:enabled="true"
 * android:process="cn.nubia.balck.number.service"
 * android:exported="true" />
 */

public class ProcessManagerProvider {

    //正在运行的数量
    public static int getRunningProcessNum(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        int size = am.getRunningAppProcesses().size();
        return size;
    }

    //获取可有进程数量,当全部应用的进程全部跑起来,总共有多少个进程
    public static int getTotalProcessNum(Context context) {
        PackageManager pm = context.getPackageManager();
        List<PackageInfo> installedPackages = pm.getInstalledPackages(
                PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
                        | PackageManager.GET_RECEIVERS | PackageManager.GET_PROVIDERS);
        int count = 0;
        for (PackageInfo info : installedPackages) {
            //不允许有重复的名字出现
            HashSet<String> list = new HashSet<String>();
            //记录app默认进程
            list.add(info.applicationInfo.processName);
            //统计activity进程
            ActivityInfo[] activities = info.activities;
            if (activities != null) {
                for (ActivityInfo activityInfo : activities) {
                    list.add(activityInfo.processName);
                }
            }
            //统计service进程
            ServiceInfo[] services = info.services;
            if (services != null) {
                for (ServiceInfo servicesInfo : services) {
                    list.add(servicesInfo.processName);
                }
            }
            //统计receiver进程
            ActivityInfo[] receivers = info.receivers;
            if (receivers != null) {
                for (ActivityInfo receiverInfo : receivers) {
                    list.add(receiverInfo.processName);
                }
            }
            //统计provider进程
            ProviderInfo[] providers = info.providers;
            if (providers != null) {
                for (ProviderInfo providerInfo : providers) {
                    list.add(providerInfo.packageName);
                }
            }
            count += list.size();
        }
        return count;
    }
}

MSG 10-15 内存信息的获取和展示

    //获取可用内存大小
    public static long getAvailMemory(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
        am.getMemoryInfo(memoryInfo);
        return memoryInfo.availMem;
    }

    //总内存大小
    public static long getTotalMemory(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
        am.getMemoryInfo(memoryInfo);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return memoryInfo.totalMem;
        } else {
            //为了兼容各个版本,可以读取proc/meminfo文件,获取总内存大小
            try {
                BufferedReader reader = new BufferedReader(new FileReader("proc/meminfo"));
                String line = reader.readLine();
                char[] chars = line.toCharArray();
                StringBuffer sb = new StringBuffer();
                for (char aChar : chars) {
                    if (aChar >= 0 && aChar <= 9) {
                        sb.append(aChar);
                    }
                }
                String strMem = sb.toString();
                long memory = Long.parseLong(strMem) * 1024;
                return memory;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return -1;
    }

MSG 10-16 获取所有正在运行的进程

    //获取所有正在运行的进程
    public static ArrayList<ProcessInfo> getRunningProcesses(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
        PackageManager pm = context.getPackageManager();
        ArrayList<ProcessInfo> list = new ArrayList<>();
        for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) {
            ProcessInfo processInfo = new ProcessInfo();
            String processName = runningAppProcess.processName;
            Debug.MemoryInfo[] processMemoryInfo = am.getProcessMemoryInfo(new int[]{runningAppProcess.pid});
            //内存大小
            long totalPrivateDirty = processMemoryInfo[0].getTotalPrivateDirty() * 1024;
            try {
                ApplicationInfo applicationInfo = pm.getApplicationInfo(processName, 0);
                String name = applicationInfo.loadLabel(pm).toString();
                Drawable icon = applicationInfo.loadIcon(pm);
                 String packageName = applicationInfo.packageName;
                processInfo.name = name;
                processInfo.icon = icon;
                processInfo.memory = totalPrivateDirty;
                processInfo.packageName = packageName;
                int flags = applicationInfo.flags;
                if ((flags & ApplicationInfo.FLAG_SYSTEM)>0) {
                    processInfo.isUserProcess = false;
                } else {
                    processInfo.isUserProcess = true;
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            list.add(processInfo);
        }
        return list;
    }

MSG 10-17 展示进程列表

MSG 11-02 studio关联第三方库
MSG 11-03 StickyListView的使用

package cn.nubia.mobilesecurityguard.activity;

import android.database.DataSetObserver;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;

import org.w3c.dom.Text;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.ButterKnife;
import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.bean.ProcessInfo;
import cn.nubia.mobilesecurityguard.utils.ProcessManagerProvider;
import cn.nubia.mobilesecurityguard.view.ProgressView;
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;

public class ProcessManagerActivity extends AppCompatActivity {


    ProgressView leftPb;
    ProgressView rightPb;
    StickyListHeadersListView lvProcessManager;
    private ProcessManagerAdapter processManagerAdapter;
    private ArrayList<ProcessInfo> runningProcesses;
    private ArrayList sysList;
    private ArrayList appList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_process_manager);

        lvProcessManager = findViewById(R.id.lv_process_manager);
        leftPb = findViewById(R.id.left_pb);
        rightPb = findViewById(R.id.right_pb);

        //正在运行的进程数
        int runningProcessNum = ProcessManagerProvider.getRunningProcessNum(this);
        //可有的进程数
        int totalProcessNum = ProcessManagerProvider.getTotalProcessNum(this);
        leftPb.setTitle("进程数:");
        rightPb.setTitle("内存:");
        leftPb.setleftText("正在运行" + runningProcessNum + "个");
        leftPb.setRightText("可有进程" + totalProcessNum + "个");

        int precent = runningProcessNum * 100 / totalProcessNum;
        leftPb.setProgress(precent);

        long availMemory = ProcessManagerProvider.getAvailMemory(this);
        long totalMemory = ProcessManagerProvider.getTotalMemory(this);
        long useMemory = totalMemory - availMemory;

        rightPb.setleftText("占用内存:" + Formatter.formatFileSize(this, useMemory));
        rightPb.setRightText("可用内存:" + Formatter.formatFileSize(this, availMemory));
        int memPrecent = (int) (useMemory * 100 / totalMemory);
        rightPb.setProgress(memPrecent);

        initData();

    }

    private void initData() {
        new Thread() {
            @Override
            public void run() {
                //super.run();
                runningProcesses = ProcessManagerProvider.getRunningProcesses(ProcessManagerActivity.this);
                sysList = new ArrayList();
                appList = new ArrayList();
                for (int i = 0; i < runningProcesses.size(); i++) {
                    if (runningProcesses.get(i).isUserProcess) {
                        appList.add(runningProcesses.get(i));
                    } else {
                        sysList.add(runningProcesses.get(i));
                    }
                }
                //清空集合
                runningProcesses.clear();
                runningProcesses.addAll(appList);
                runningProcesses.addAll(sysList);

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        processManagerAdapter = new ProcessManagerAdapter();
                        lvProcessManager.setAdapter(processManagerAdapter);
                    }
                });
            }
        }.start();
    }

    class ProcessManagerAdapter extends BaseAdapter implements StickyListHeadersAdapter {

        @Override
        public View getHeaderView(int position, View convertView, ViewGroup parent) {
            TitleHolder titleHolder = null;
            if (convertView == null) {
                convertView = View.inflate(ProcessManagerActivity.this, R.layout.item_app_title, null);
                titleHolder = new TitleHolder();
                titleHolder.tvTitle = convertView.findViewById(R.id.tv_title);
                //将holder保存和当前布局绑定
                convertView.setTag(titleHolder);
            } else {
                titleHolder = (TitleHolder) convertView.getTag();
            }
            ProcessInfo info = getItem(position);
            if (info.isUserProcess) {
                titleHolder.tvTitle.setText("用户进程");
            } else {
                titleHolder.tvTitle.setText("系统进程");
            }
            return convertView;
        }

        @Override
        public long getHeaderId(int position) {
            return getItem(position).isUserProcess ? 0 : 1;
        }

        @Override
        public int getCount() {
            return runningProcesses.size();
        }

        @Override
        public ProcessInfo getItem(int position) {
            return runningProcesses.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = View.inflate(ProcessManagerActivity.this, R.layout.item_process_manager, null);
                holder = new ViewHolder();
                holder.ivIcon = convertView.findViewById(R.id.iv_icon);
                holder.tvName = convertView.findViewById(R.id.tv_name);
                holder.tvMemory = convertView.findViewById(R.id.tv_memory);
                holder.checkBox = convertView.findViewById(R.id.cb_checkbox);
                //将holder保存和当前布局绑定
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            ProcessInfo info = getItem(position);
            holder.ivIcon.setImageDrawable(info.icon);
            holder.tvName.setText(info.name);
            holder.tvMemory.setText(Formatter.formatFileSize(getApplicationContext(), info.memory));
            return convertView;
        }
    }

    static class ViewHolder {
        public ImageView ivIcon;
        public TextView tvName;
        public TextView tvMemory;
        public CheckBox checkBox;
    }

    static class TitleHolder {
        public TextView tvTitle;
    }
}

MSG 11-04 单选效果实现

1、禁用checkbox的点击事件:
        android:clickable="false"
        android:focusable="false"
        android:focusableInTouchMode="false"
2、让listview可以点击
3、点击条目时切换checkbox的状态
        lvProcessManager.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                CheckBox check = view.findViewById(R.id.cb_checkbox);
                check.setChecked(!check.isChecked());

                ProcessInfo info = runningProcesses.get(position);
                info.isChecked = !info.isChecked;
            }
        });
    public boolean isChecked;记录状态

MSG 11-05 全选和反选功能实现

全选:
                for (ProcessInfo info : runningProcesses) {
                    info.isChecked = true;
                }
                processManagerAdapter.notifyDataSetChanged();
反选:
                for (ProcessInfo info : runningProcesses) {
                    info.isChecked = !info.isChecked;
                }
                processManagerAdapter.notifyDataSetChanged();

MSG 11-06 跳过手机卫士选中

getView中:
            if (info.packageName.equals(getPackageName())) {
                holder.checkBox.setVisibility(View.GONE);
            } else {
                holder.checkBox.setVisibility(View.VISIBLE);
                holder.checkBox.setChecked(info.isChecked);
            }
在点击条目时跳过此对象:
                CheckBox check = view.findViewById(R.id.cb_checkbox);
                ProcessInfo info = runningProcesses.get(position);

                if (info.packageName.equals(getPackageName())) {
                    return;
                } else {
                    check.setChecked(!check.isChecked());
                    info.isChecked = !info.isChecked;
                }
全选和反选:跳过
            case R.id.bt_fan_xuan:
                for (ProcessInfo info : runningProcesses) {
                    if (info.packageName.equals(getPackageName())) {
                        return;
                    }
                    info.isChecked = !info.isChecked;
                }
                processManagerAdapter.notifyDataSetChanged();
                break;
            case R.id.bt_all_xuan:
                for (ProcessInfo info : runningProcesses) {
                    if (info.packageName.equals(getPackageName())) {
                        return;
                    }
                    info.isChecked = true;
                }
                processManagerAdapter.notifyDataSetChanged();
                break;

MSG 11-07 一键清理&界面刷新

                ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
                for (ProcessInfo info : runningProcesses) {
                    if (info.isChecked) {
                        am.killBackgroundProcesses(info.packageName);
                    }
                    info.isChecked = !info.isChecked;
                }
需要权限:
    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />

                ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
                //会出现并发修改异常
                ArrayList<ProcessInfo> killedList = new ArrayList<>();
                for (ProcessInfo info : runningProcesses) {
                    if (info.isChecked) {
                        am.killBackgroundProcesses(info.packageName);
                        //runningProcesses.remove(info);
                        killedList.add(info);
                    }
                }
                runningProcesses.removeAll(killedList);
                processManagerAdapter.notifyDataSetChanged();
    

MSG 11-08 一键清理友好提示&进程内存信息更新

                ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
                //会出现并发修改异常
                ArrayList<ProcessInfo> killedList = new ArrayList<>();
                for (ProcessInfo info : runningProcesses) {
                    if (info.isChecked) {
                        am.killBackgroundProcesses(info.packageName);
                        //runningProcesses.remove(info);
                        killedList.add(info);
                    }
                }
                long saveMemory= 0;
                for (ProcessInfo info : killedList) {
                    saveMemory += info.memory;
                }
                runningProcesses.removeAll(killedList);
                processManagerAdapter.notifyDataSetChanged();
                String toast = String.format("一共杀死%d个进程,释放%sM内存空间", killedList.size(), Formatter.formatFileSize(this, saveMemory));
                Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();

MSG 11-09 SlidingDrawer抽屉效果实现

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:nubia="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.ProcessManagerActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            style="@style/TitleStyle"
            android:text="进程管理" />

        <ImageView
            android:id="@+id/iv_clean"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="20dp"
            android:src="@drawable/clean_normal" />

    </RelativeLayout>

    <cn.nubia.mobilesecurityguard.view.ProgressView
        android:id="@+id/left_pb"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </cn.nubia.mobilesecurityguard.view.ProgressView>

    <cn.nubia.mobilesecurityguard.view.ProgressView
        android:id="@+id/right_pb"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </cn.nubia.mobilesecurityguard.view.ProgressView>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <se.emilsjolander.stickylistheaders.StickyListHeadersListView
            android:id="@+id/lv_process_manager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </se.emilsjolander.stickylistheaders.StickyListHeadersListView>

        <SlidingDrawer
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:content="@+id/content"
            android:handle="@+id/handle">

            <RelativeLayout
                android:id="@id/handle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/drawer_bg">

                <ImageView
                    android:id="@+id/iv_arrow1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerHorizontal="true"
                    android:layout_marginTop="20dp"
                    android:src="@drawable/drawer_arrow_up" />

                <ImageView
                    android:id="@+id/iv_arrow2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/iv_arrow1"
                    android:layout_centerHorizontal="true"
                    android:src="@drawable/drawer_arrow_up" />

            </RelativeLayout>

            <LinearLayout
                android:id="@id/content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="horizontal">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="20dp"
                    android:text="进程管理设置" />

                <cn.nubia.mobilesecurityguard.view.SettingItemView
                    android:id="@+id/show_sys_process"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dp"
                    nubia:nubia_background="first"
                    nubia:nubia_show_toggle="true"
                    nubia:nubia_title="显示系统进程">

                </cn.nubia.mobilesecurityguard.view.SettingItemView>

                <cn.nubia.mobilesecurityguard.view.SettingItemView
                    android:id="@+id/screen_off_clean"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    nubia:nubia_background="last"
                    nubia:nubia_show_toggle="true"
                    nubia:nubia_title="锁屏自动清理">

                </cn.nubia.mobilesecurityguard.view.SettingItemView>
            </LinearLayout>

        </SlidingDrawer>

    </FrameLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/bt_all_xuan"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="全选" />

        <Button
            android:id="@+id/bt_fan_xuan"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="反选" />

    </LinearLayout>


</LinearLayout>

MSG 11-10 抽屉箭头动画

    private void startAnimal() {
        AlphaAnimation animation1 = new AlphaAnimation(0.2f, 1);
        animation1.setDuration(500);
        animation1.setRepeatCount(Animation.INFINITE);
        animation1.setRepeatMode(Animation.REVERSE);
        ivArrow1.startAnimation(animation1);

        AlphaAnimation animation2 = new AlphaAnimation(1, 0.2f);
        animation2.setDuration(500);
        animation2.setRepeatCount(Animation.INFINITE);
        animation2.setRepeatMode(Animation.REVERSE);
        ivArrow2.startAnimation(animation2);
    }

向下停止动画:
        sdDrawer.setOnDrawerOpenListener(new SlidingDrawer.OnDrawerOpenListener() {
            @Override
            public void onDrawerOpened() {
                ivArrow1.setImageResource(R.drawable.drawer_arrow_down);
                ivArrow2.setImageResource(R.drawable.drawer_arrow_down);
                ivArrow1.clearAnimation();
                ivArrow2.clearAnimation();
            }
        });
        sdDrawer.setOnDrawerCloseListener(new SlidingDrawer.OnDrawerCloseListener() {
            @Override
            public void onDrawerClosed() {
                ivArrow1.setImageResource(R.drawable.drawer_arrow_up);
                ivArrow2.setImageResource(R.drawable.drawer_arrow_down);
                startAnimal();
            }
        });

MSG 11-11 是否显示系统进程

        @Override
        public int getCount() {
            if (isShowSysProcess) {
                return runningProcesses.size();
            } else {
                return appList.size();
            }
        }
打开关闭抽屉时会导致listview隐藏,会自动刷新listview

MSG 11-12 锁屏自动清理&定时器Timer

判断锁屏服务是否开启
            case R.id.screen_off_clean:
                if (ServiceStatusUtils.isServiceRunning(this,AutoCleanService.class)) {
                    stopService(new Intent(this,AutoCleanService.class));
                } else {
                    startService(new Intent(this,AutoCleanService.class));
                }
                break;
创建服务:
package cn.nubia.mobilesecurityguard.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.util.Log;

import cn.nubia.mobilesecurityguard.utils.ProcessManagerProvider;

public class AutoCleanService extends Service {

    private AutoKillReceiver receiver;

    public AutoCleanService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        //动态注册广播,监听锁屏
        receiver = new AutoKillReceiver();
        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        registerReceiver(receiver,filter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
    }

    class AutoKillReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("sunyang","屏幕关闭了");
            ProcessManagerProvider.killAllProcesses(context);
        }
    }
}

计时器关闭:
package cn.nubia.mobilesecurityguard.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.util.Log;

import java.util.Timer;
import java.util.TimerTask;

import cn.nubia.mobilesecurityguard.utils.ProcessManagerProvider;

public class AutoCleanService extends Service {

    private AutoKillReceiver receiver;
    private Timer timer;

    public AutoCleanService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        //动态注册广播,监听锁屏
        receiver = new AutoKillReceiver();
        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        registerReceiver(receiver, filter);

        //定时清理,参2:第一次执行任务的延时时间,参3:每次执行任务的时间间隔
        timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {

            }
        }, 0, 5000);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
        timer.cancel();
    }

    class AutoKillReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("sunyang", "屏幕关闭了");
            ProcessManagerProvider.killAllProcesses(context);
        }
    }
}

MSG 11-13 程序锁页面开发

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.AppLockActivity">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

        <Button
            android:id="@+id/bt_unlock"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/tab_left_default"
            android:text="未加锁" />

        <Button
            android:id="@+id/bt_lock"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/tab_right_pressed"
            android:text="已加锁" />

    </LinearLayout>

    <TextView
        android:id="@+id/tv_lock_number"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="@color/gray"
        android:text="未加锁应用(0)" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/lv_unlock"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></ListView>

        <ListView
            android:id="@+id/lv_lock"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone"></ListView>

    </FrameLayout>

</LinearLayout>

MSG 12-02 程序锁布局开发&切换已加锁未加锁页面

MSG 12-03 程序锁数据库封装

package cn.nubia.mobilesecurityguard.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class AppLockOpenHelper extends SQLiteOpenHelper {

    public AppLockOpenHelper(Context context) {
        super(context, "applock.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建表
        String sql = "create table applock(_id integer primary key autoincrement," +
                " package varchar(50))";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

增删改查:
package cn.nubia.mobilesecurityguard.db.dao;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.util.ArrayList;

import cn.nubia.mobilesecurityguard.bean.BlackNumberInfo;
import cn.nubia.mobilesecurityguard.db.AppLockOpenHelper;
import cn.nubia.mobilesecurityguard.db.BlackNumberOpenHelper;

/**
 * 程序锁-单例设计模式
 * <p>
 * 两种方式进行初始化:
 * 1、懒汉式:线程安全问题 1.给方法加同步锁 synchronized 2.创建对象的代码块加同步锁
 * //2、公开方法,返回单例对象
 * public static BlackNumberDao getInstance(Context context) {
 * if (mInstance == null) {
 * synchronized (BlackNumberDao.class) {
 * if (mInstance == null) {
 * mInstance =  new BlackNumberDao(context);
 * }
 * }
 * }
 * return mInstance;
 * }
 * 2、饿汉式:
 * private static BlackNumberDao mInstance = new BlackNumberDao();
 */
public class AppLockDao {

    private final AppLockOpenHelper helper;

    //3、声明一个静态对象
    private static AppLockDao mInstance;


    //1、构造方法私有
    private AppLockDao(Context context) {
        helper = new AppLockOpenHelper(context);
    }

    //2、公开方法,返回单例对象
    public static AppLockDao getInstance(Context context) {
        if (mInstance == null) {
            synchronized (AppLockDao.class) {
                if (mInstance == null) {
                    mInstance = new AppLockDao(context);
                }
            }
        }
        return mInstance;
    }

    public boolean insert(String packageName) {
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("packageName", packageName);
        //返回插入记录的id,-1表示失败
        long insert = db.insert("applock", null, values);
        db.close();
        return insert != -1;
    }

    public boolean delete(String packageName) {
        SQLiteDatabase db = helper.getWritableDatabase();
        //影响的行数,0表示未删除
        int delete = db.delete("applock", "packageName=?", new String[]{packageName});
        db.close();
        return delete > 0;
    }

    //查询是否在数据库
    public boolean query(String packageName) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("applock", new String[]{"packageName"}, "packageName=?", new String[]{packageName}, null, null, null);
        boolean isExit = false;
        if (cursor != null) {
            if (cursor.moveToNext()) {
                isExit = true;
            }
            cursor.close();
        }
        db.close();
        return isExit;
    }

    //查询所有号码的集合
    public ArrayList<String> queryAll() {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.query("applock", new String[]{"packageName"}, null, null, null, null, null);
        ArrayList<String> list = new ArrayList<>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String number = cursor.getString(0);
                list.add(number);
            }
            cursor.close();
        }
        db.close();
        return list;
    }

    public int getCount() {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select count(*) from applock", null);
        int count = 0;
        try {
            if (cursor != null) {
                if (cursor.moveToNext()) {
                    count = cursor.getInt(0);
                }
                cursor.close();
            }
            db.close();
            return count;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
}

MSG 12-04 加载数据&展示未加锁列表
MSG 12-05 展示已加锁列表&公用一个adapter
MSG 12-06 加锁和解锁逻辑处理

package cn.nubia.mobilesecurityguard.activity;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;

import cn.nubia.mobilesecurityguard.R;
import cn.nubia.mobilesecurityguard.bean.AppManagerInfo;
import cn.nubia.mobilesecurityguard.db.dao.AppLockDao;
import cn.nubia.mobilesecurityguard.utils.AppInfoProvider;

public class AppLockActivity extends AppCompatActivity implements View.OnClickListener {

    private ListView lvLock;
    private ListView lvUnLock;
    private Button btLock;
    private Button btUnLock;
    private AppLockDao dao;
    private ArrayList<AppManagerInfo> mUnLockList;
    private ArrayList<AppManagerInfo> mLockList;
    private AppLockAdapter unlockAdapter;
    private AppLockAdapter lockAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_lock);

        btLock = findViewById(R.id.bt_lock);
        btUnLock = findViewById(R.id.bt_unlock);
        lvLock = findViewById(R.id.lv_lock);
        lvUnLock = findViewById(R.id.lv_unlock);
        btLock.setOnClickListener(this);
        btUnLock.setOnClickListener(this);
        dao = AppLockDao.getInstance(this);

        initData();
    }

    private void initData() {
        new Thread() {
            @Override
            public void run() {
                //super.run();
                //1、获取所以已安装app
                ArrayList<AppManagerInfo> installedApps = AppInfoProvider.getInstalledApps(AppLockActivity.this);
                //2、判读是否已加锁,如果已加锁放在已加锁集合
                mUnLockList = new ArrayList<>();
                mLockList = new ArrayList<>();
                for (AppManagerInfo installedApp : installedApps) {
                    if (dao.query(installedApp.packageName)) {
                        mLockList.add(installedApp);
                    } else {
                        mUnLockList.add(installedApp);
                    }
                }

                sort();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        unlockAdapter = new AppLockAdapter(false);
                        lvUnLock.setAdapter(unlockAdapter);

                        lockAdapter = new AppLockAdapter(true);
                        lvLock.setAdapter(lockAdapter);

                    }
                });

            }
        }.start();

    }

    private void sort() {
        //给两个集合按照字符进行排序
        Collections.sort(mUnLockList, new Comparator<AppManagerInfo>() {
            @Override
            public int compare(AppManagerInfo o1, AppManagerInfo o2) {
                return o1.name.compareTo(o2.name);
            }
        });
        Collections.sort(mLockList, new Comparator<AppManagerInfo>() {
            @Override
            public int compare(AppManagerInfo o1, AppManagerInfo o2) {
                return o1.name.compareTo(o2.name);
            }
        });
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_lock:
                //显示加锁的listview
                lvLock.setVisibility(View.VISIBLE);
                lvUnLock.setVisibility(View.GONE);
                btLock.setBackgroundResource(R.drawable.tab_right_pressed);
                btUnLock.setBackgroundResource(R.drawable.tab_left_default);
                break;
            case R.id.bt_unlock:
                lvLock.setVisibility(View.GONE);
                lvUnLock.setVisibility(View.VISIBLE);
                btLock.setBackgroundResource(R.drawable.tab_right_default);
                btUnLock.setBackgroundResource(R.drawable.tab_left_pressed);
                break;
        }
    }

    class AppLockAdapter extends BaseAdapter {
        //标记已加锁
        private boolean isLock;

        public AppLockAdapter(boolean isLock) {
            this.isLock = isLock;
        }

        @Override
        public int getCount() {
            if (isLock) {
                return mLockList.size();
            } else {
                return mUnLockList.size();
            }
        }

        @Override
        public AppManagerInfo getItem(int position) {
            if (isLock) {
                return mLockList.get(position);
            } else {
                return mUnLockList.get(position);
            }
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = View.inflate(AppLockActivity.this, R.layout.item_appunlock, null);
                holder = new ViewHolder();
                holder.ivIcon = convertView.findViewById(R.id.iv_icon);
                holder.tvName = convertView.findViewById(R.id.tv_name);
                holder.ivUnlock = convertView.findViewById(R.id.iv_unlock);
                //将holder保存和当前布局绑定
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            final AppManagerInfo info = getItem(position);
            holder.ivIcon.setImageDrawable(info.icon);
            holder.tvName.setText(info.name);
            if (isLock) {
                holder.ivUnlock.setImageResource(R.drawable.list_button_lock_pressed);
                holder.ivUnlock.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //1、从数据库移除
                        dao.insert(info.packageName);
                        //2、从已加锁集合移除
                        mLockList.remove(info);
                        //3、给未加锁集合添加对象
                        mUnLockList.add(info);
                        sort();
                        //4、刷新listview
                        unlockAdapter.notifyDataSetChanged();
                        lockAdapter.notifyDataSetChanged();
                    }
                });
            } else {
                //未加锁
                holder.ivUnlock.setImageResource(R.drawable.list_button_unlock_pressed);
                holder.ivUnlock.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //1、添加到已加锁数据库
                        dao.insert(info.packageName);
                        //2、从未加锁集合移除
                        mUnLockList.remove(info);
                        //3、给已加锁集合添加对象
                        mLockList.add(info);
                        sort();
                        //4、刷新listview
                        unlockAdapter.notifyDataSetChanged();
                        lockAdapter.notifyDataSetChanged();
                    }
                });
            }
            return convertView;
        }
    }

    static class ViewHolder {
        public ImageView ivIcon;
        public TextView tvName;
        public ImageView ivUnlock;
    }
}

MSG 12-07 给集合排序

                //给两个集合按照字符进行排序
                Collections.sort(mUnLockList, new Comparator<AppManagerInfo>() {
                    @Override
                    public int compare(AppManagerInfo o1, AppManagerInfo o2) {
                        return o1.name.compareTo(o2.name);
                    }
                });
                Collections.sort(mLockList, new Comparator<AppManagerInfo>() {
                    @Override
                    public int compare(AppManagerInfo o1, AppManagerInfo o2) {
                        return o1.name.compareTo(o2.name);
                    }
                });
问题:增加数据时,还是排在最后,需要刷新排序
自定义集合排序

MSG 12-08 加锁解锁动画

在构造方法中初始化动画
    class AppLockAdapter extends BaseAdapter {
        //标记已加锁
        private boolean isLock;
        private final TranslateAnimation translateAnimationRight;
        private final TranslateAnimation translateAnimationLeft;

        public AppLockAdapter(boolean isLock) {
            this.isLock = isLock;
            //初始化平移动画
            translateAnimationRight = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
            translateAnimationRight.setDuration(500);

            translateAnimationLeft = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
            translateAnimationRight.setDuration(500);
        }

        @Override
        public int getCount() {
            if (isLock) {
                return mLockList.size();
            } else {
                return mUnLockList.size();
            }
        }

        @Override
        public AppManagerInfo getItem(int position) {
            if (isLock) {
                return mLockList.get(position);
            } else {
                return mUnLockList.get(position);
            }
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = View.inflate(AppLockActivity.this, R.layout.item_appunlock, null);
                holder = new ViewHolder();
                holder.ivIcon = convertView.findViewById(R.id.iv_icon);
                holder.tvName = convertView.findViewById(R.id.tv_name);
                holder.ivUnlock = convertView.findViewById(R.id.iv_unlock);
                //将holder保存和当前布局绑定
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            final AppManagerInfo info = getItem(position);
            holder.ivIcon.setImageDrawable(info.icon);
            holder.tvName.setText(info.name);
            final View finalConvertView = convertView;
            if (isLock) {
                holder.ivUnlock.setImageResource(R.drawable.list_button_lock_pressed);
                holder.ivUnlock.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //动画是异步执行,需要监听动画结束的事件,在动画结束时再刷新集合
                        translateAnimationLeft.setAnimationListener(new Animation.AnimationListener() {
                            @Override
                            public void onAnimationStart(Animation animation) {

                            }

                            @Override
                            public void onAnimationEnd(Animation animation) {
                                //1、从数据库移除
                                dao.insert(info.packageName);
                                //2、从已加锁集合移除
                                mLockList.remove(info);
                                //3、给未加锁集合添加对象
                                mUnLockList.add(info);
                                sort();
                                //4、刷新listview
                                unlockAdapter.notifyDataSetChanged();
                                lockAdapter.notifyDataSetChanged();
                            }

                            @Override
                            public void onAnimationRepeat(Animation animation) {

                            }
                        });
                        finalConvertView.startAnimation(translateAnimationLeft);
                    }
                });
            } else {
                //未加锁
                holder.ivUnlock.setImageResource(R.drawable.list_button_unlock_pressed);
                holder.ivUnlock.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        translateAnimationRight.setAnimationListener(new Animation.AnimationListener() {
                            @Override
                            public void onAnimationStart(Animation animation) {

                            }

                            @Override
                            public void onAnimationEnd(Animation animation) {
                                //1、添加到已加锁数据库
                                dao.insert(info.packageName);
                                //2、从未加锁集合移除
                                mUnLockList.remove(info);
                                //3、给已加锁集合添加对象
                                mLockList.add(info);
                                sort();
                                //4、刷新listview
                                unlockAdapter.notifyDataSetChanged();
                                lockAdapter.notifyDataSetChanged();
                            }

                            @Override
                            public void onAnimationRepeat(Animation animation) {

                            }
                        });
                        finalConvertView.startAnimation(translateAnimationRight);


                    }
                });
            }
            return convertView;
        }
    }

MSG 12-09 程序锁数量更新

    //只要listview刷新,就需要调用一次
    private void updateLockNum(boolean isLock) {
        if (isLock) {
            tvLockNumber.setText("已加锁("+mLockList.size()+")");
        } else {
            tvLockNumber.setText("未加锁("+mUnLockList.size()+")");
        }
    }
在第一次设置数据和每次刷新数据时都会调用
        @Override
        public int getCount() {
            updateLockNum(isLock);
            if (isLock) {
                return mLockList.size();
            } else {
                return mUnLockList.size();
            }
        }

MSG 12-10 辅助功能服务介绍

/**
 * 程序锁原理:
 * 1、监听当前屏幕展示的页面
 * 2、判断该页面是否需要加锁
 * 3、如果加锁,就跳到输入密码页面
 * 4、密码验证成功后,才能正常使用app
 */
配置程序锁服务:
https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityService

        <service
            android:name=".service.AppLockAccessibilityService"
            android:label="@string/appLock_accessibility_service"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>

            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibilityservice" />
        </service>

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeViewClicked|typeViewFocused|typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:description="辅助功能" />

package cn.nubia.mobilesecurityguard.service;


import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;

public class AppLockAccessibilityService extends AccessibilityService {
    public AppLockAccessibilityService() {
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

    }

    @Override
    public void onInterrupt() {

    }
}

MSG 12-11 程序锁服务开关控制

    private void startAccessibilityActivity() {
        Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
        startActivity(intent);
    }
  
    @Override
    protected void onStart() {
        super.onStart();
        boolean serviceRunning = ServiceStatusUtils.isServiceRunning(this, AppLockAccessibilityService.class);
        click6.setToggleOn(serviceRunning);
    }

MSG 12-12 跳到输入密码页面

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            String packageName = event.getPackageName().toString();
            boolean isExist = dao.query(packageName);
            //跳到输入密码界面
            if (isExist) {
                Intent intent = new Intent(this, AppGraLockActivity.class);
                intent.putExtra("packageName", packageName);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        }
    }

package cn.nubia.mobilesecurityguard.activity;

import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;

import cn.nubia.mobilesecurityguard.R;

public class AppGraLockActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_gra_lock);

        ImageView ivIcon = findViewById(R.id.iv_icon);
        TextView tvName = findViewById(R.id.tv_name);
        findViewById(R.id.et_pwd);

        String packageName = getIntent().getStringExtra("packageName");
        PackageManager pm = getPackageManager();
        try {
            ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, 0);
            Drawable icon = applicationInfo.loadIcon(pm);
            String name = applicationInfo.loadLabel(pm).toString();
            ivIcon.setImageDrawable(icon);
            tvName.setText(name);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

MSG 12-13 拦截物理返回键跳到桌面

    @Override
    public void onBackPressed() {
        //super.onBackPressed();
        //隐式意图跳到桌面
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        startActivity(intent);
        finish();
    }

MSG 12-14 验证密码&通过自定义广播传递数据

        btOk.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String pwd = etPwd.getText().toString().trim();
                if (!TextUtils.isEmpty(pwd)) {
                    if (pwd.equals("123")) {
                        finish();
                    }
                }
            }
        });

结束后,在服务中还会开启,所以需要从activity传递给服务
                        //发送自定义广播,传递数据
                        Intent intent = new Intent();
                        intent.setAction("action_skip_package");
                        intent.putExtra("packageName", packageName);
                        sendBroadcast(intent);


package cn.nubia.mobilesecurityguard.service;


import android.accessibilityservice.AccessibilityService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;

import cn.nubia.mobilesecurityguard.activity.AppGraLockActivity;
import cn.nubia.mobilesecurityguard.db.dao.AppLockDao;

public class AppLockAccessibilityService extends AccessibilityService {

    private AppLockDao dao;
    private AppLockReceiver receiver;
    private String packageName;

    public AppLockAccessibilityService() {

    }

    @Override
    public void onCreate() {
        super.onCreate();
        dao = AppLockDao.getInstance(this);

        receiver = new AppLockReceiver();
        IntentFilter filter = new IntentFilter("action_skip_package");
        registerReceiver(receiver, filter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
        receiver = null;
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            String packageName = event.getPackageName().toString();
            boolean isExist = dao.query(packageName);
            //跳到输入密码界面
            if (isExist && !packageName.equals(packageName)) {
                Intent intent = new Intent(this, AppGraLockActivity.class);
                intent.putExtra("packageName", packageName);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        }
    }

    @Override
    public void onInterrupt() {

    }


    class AppLockReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            packageName = intent.getStringExtra("packageName");
            Log.e("sunyang", "跳过验证");
        }
    }
}

MSG 12-15 通过activity启动模式解决任务栈bug
MSG 12-16 不出现在最近任务列表中
MSG 12-17 AsyncTask 的基本使用
MSG 12-18 AsyncTask 的高级使用
MSG 12-19 流量统计原理

MSG 13-02 流量统计—初始化数据
MSG 13-03 RecyclerView的基本使用
MSG 13-04 流量统计添加进度条
MSG 13-05 杀毒原理&病毒数据库封装
MSG 13-06 扫描app判断是否是病毒
MSG 13-07 展示病毒扫描列表
MSG 13-08 扫描过程中刷新RecyclerView
MSG 13-09 假冒病毒并测试
MSG 13-10 CircleProgress的使用
MSG 13-11 手机杀毒进度更新
MSG 13-12 展示扫描病毒结果
MSG 13-13 开门动画布局
MSG 13-14 获取布局所展示的截图
MSG 13-15 截取图片一半进行展示
MSG 13-16 开门动画实现
MSG 13-17 重新扫描实现
MSG 13-18 卸载病毒
MSG 13-19 启用&禁用扫描按钮
MSG 13-20 停止异步任务
MSG 13-21 横竖屏切换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值