实现目标:1、使用adb命令安装apk是禁止部分apk安装
2、允许用户手动修改黑名单
3、界面展示黑名单内容
1、创建一个存放黑名单的数据库,在frameworks/base/services/core/java/com/android/server/pm下创建PackagesDatabaseHelper.java,用来实现创建存放黑名单的数据库,代码如下:
package com.android.server.pm;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.util.Log;
public class PackagesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称
private static final String DATABASE_NAME = "/data/packagesdatabase.db";
// 数据库版本
private static final int DATABASE_VERSION = 1;
public PackagesDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
if (!isTableExists("mytable",db)) {
Log.d("++++++++++++++++++++++++","--------------------------------");
// 创建表的操作
String CREATE_TABLE = "CREATE TABLE " + "mytable" + "("
+ "ID" + " INTEGER PRIMARY KEY AUTOINCREMENT," + "NAME" + " TEXT"
+ ")";
db.execSQL(CREATE_TABLE);
// 插入数据
String INSERT_DATA = "INSERT INTO " + "mytable" + " (NAME) VALUES ('mypopup')";
db.execSQL(INSERT_DATA);
}
// db.execSQL(CREATE_TABLE);
// setDefaultLabel(db); // 传递当前的数据库实例
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 更新数据库
db.execSQL("DROP TABLE IF EXISTS " + "mytable");
onCreate(db);
}
public boolean isTableExists(String tableName,SQLiteDatabase db) {
Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name=?", new String[] {tableName});
Log.d("++++++++++++++++++++++++++++++++++++++","isTableExists");
if (cursor != null) {
if(cursor.getCount() > 0) {
cursor.close();
Log.d("++++++++++++++++++++++++++++++++++++++","isTableExists=true");
return true;
}
cursor.close();
}
Log.d("++++++++++++++++++++++++++++++++++++++","isTableExists=false");
return false;
}
}
2、adb安装apk会最终会在frameworks/base/services/core/java/com/android/server/pm下的PackageManagerService.java中实现,所以我们需要在这个类里面实现我们的黑名单判断
2-1、首先在PackageManagerService.java中写好需要的方法
import android.database.Cursor;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.WindowManager;
import android.database.sqlite.SQLiteDatabase;
import com.android.server.pm.PackagesDatabaseHelper;
import android.widget.Toast;
2-1、 在PackageManagerService.java中的preparePackageLI()方法中进行黑名单判断,代码如下:
if (parsedPackage != null) {
String installAppStr = "com.leyyy.mypopup; com.lrs.zfmobile";
List<String> appList = new ArrayList<String>();
PackagesDatabaseHelper dbHelper = new PackagesDatabaseHelper(mContext);
// 获取可读数据库
SQLiteDatabase db = dbHelper.getReadableDatabase();
// 执行查询
Cursor cursor = db.query("mytable", null, null, null, null, null, null);
//Log.d("-------------------------------------------",""+isTableEmpty(db,"mytable"));
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndexOrThrow("ID"));
Log.d("-------------------------------ID","---------------------------"+id);
String name = cursor.getString(cursor.getColumnIndexOrThrow("NAME"));
Log.d("-------------------------------NAME","---------------------------"+name);
// 创建数据模型并添加到List
appList.add(name);
}
Log.d("-------------------------------DataBase","---------------------------"+appList);
// 关闭Cursor
cursor.close();
// 关闭数据库连接
db.close();
if(!TextUtils.isEmpty(installAppStr)){
// appList = Arrays.asList(installAppStr.split(";"));
if(appList.size()>0){
String currentPkgName = parsedPackage.getPackageName();
// if((installMode == 1 && appList.contains(currentPkgName)) || (installMode == 2 && !appList.contains(currentPkgName)){
for (int i = 0; i < appList.size(); i++) {
if(currentPkgName.contains(appList.get(i))){
Log.d("---------------------------------------------------------------------", "Secure check install_app_white_Black_list is NULL");
res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Restrict installation applications:" + currentPkgName);
Toast.makeText(mContext, "安装失败", Toast.LENGTH_LONG).show();
}else {
Log.d("---------------------------------------------------------------------fyTest", "Secure check install_app_white_Black_list is NULL");
}
}
}
}
}
3、 就该在设置里面写一个可以增加黑名单的界面,首先我先在top_level_settings.xml里面增加了一个选项,位置:packages/apps/Settings/res/xml/top_level_settings.xml,代码如下:
<com.android.settings.widget.HomepagePreference
android:fragment="com.android.settings.myceshi.MySettingsCeshi"
android:icon="@drawable/ic_help"
android:key="top_level_my_setting"
android:order="40"
android:title="无标题"
android:summary="无标题"/>
4、然后我在packages/apps/Settings/src/com/android/settings下新建了一个文件夹myceshi,在myceshi文件夹里新建了一个MySettingsCeshi.java。
package com.android.settings.myceshi;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.WindowManager;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import android.widget.Toast;
import android.app.Dialog;
@SearchIndexable
public class MySettingsCeshi extends DashboardFragment implements Preference.OnPreferenceClickListener,Preference.OnPreferenceChangeListener{
private static final String TAG = "mysettingsceshi";
private Toast mDevHitToast;
private SwitchPreference mShow;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mDevHitToast = null;
mShow = (SwitchPreference) findPreference("myFunction2");
mShow.setOnPreferenceChangeListener(this);
Preference mPreference = findPreference("myFunction1");
mPreference.setOnPreferenceClickListener(this);
}
@Override
public boolean onPreferenceClick(Preference preference) {
Log.e("onPreferenceClick","走了点击时间2");
Toast.makeText(getActivity(), "Preference Clicked", Toast.LENGTH_SHORT).show();
return true;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean show = (boolean)newValue;
if(show==true){
Toast.makeText(getActivity(), "已打开", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(getActivity(), "已关闭", Toast.LENGTH_SHORT).show();
}
return true;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.my_settings_ceshi;
}
@Override
public int getMetricsCategory() {
return 5;
}
/**
* For Search.
*/
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.my_settings_ceshi);
}
5、 还需要建一个给MySettingsCeshi.java使用的xml文件视图,在packages/apps/Settings/res/xml下建立my_settings_ceshi.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="展示内容">
<Preference
android:key="myFunction1"
android:order="44"
android:title="功能其一"
android:summary="功能其一"/>
<SwitchPreference
android:key="myFunction2"
android:order="45"
android:title="功能其二"
android:summary="功能其二"/>
<com.android.settings.myceshi.PrivateDialogPreference
android:key="myFunction3"
android:order="46"
android:title="功能其三"
android:summary="功能其三"
android:dialogLayout="@layout/my_dialog"/>
<com.android.settings.myceshi.PrivateDialogPreference2
android:key="myFunction4"
android:order="47"
android:title="功能其四"
android:summary="功能其四"
android:dialogLayout="@layout/my_dialog2"/>
<com.android.settings.myceshi.PrivateDialogPreference3
android:key="myFunction5"
android:order="48"
android:title="网络黑名单"
android:summary="以太网络控制"
android:dialogLayout="@layout/my_dialog3"/>
<com.android.settings.myceshi.PrivateDialogPreference4
android:key="myFunction6"
android:order="49"
android:title="软件黑名单"
android:summary="软件安装控制"
android:dialogLayout="@layout/my_dialog4"/>
</PreferenceScreen>
6、因为里面建的数据库设置里面没办法直接调用,所以需要再这里面再建立一个数据库,只需要调用同一个xxx.db文件就行,所以我在packages/apps/Settings/src/com/android/settings/myceshi下新建了一个数据库PackagesDatabaseHelper.java。
package com.android.server;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.util.Log;
public class PackagesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称
private static final String DATABASE_NAME = "/data/packagesdatabase.db";
// 数据库版本
private static final int DATABASE_VERSION = 1;
public PackagesDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
if (!isTableExists("mytable",db)) {
Log.d("++++++++++++++++++++++++","--------------------------------");
// 创建表的操作
String CREATE_TABLE = "CREATE TABLE " + "mytable" + "("
+ "ID" + " INTEGER PRIMARY KEY AUTOINCREMENT," + "NAME" + " TEXT"
+ ")";
db.execSQL(CREATE_TABLE);
// 插入数据
String INSERT_DATA = "INSERT INTO " + "mytable" + " (NAME) VALUES ('mypopup')";
db.execSQL(INSERT_DATA);
}
// db.execSQL(CREATE_TABLE);
// setDefaultLabel(db); // 传递当前的数据库实例
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 更新数据库
db.execSQL("DROP TABLE IF EXISTS " + "mytable");
onCreate(db);
}
public boolean isTableExists(String tableName,SQLiteDatabase db) {
Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name=?", new String[] {tableName});
Log.d("++++++++++++++++++++++++++++++++++++++","isTableExists");
if (cursor != null) {
if(cursor.getCount() > 0) {
cursor.close();
Log.d("++++++++++++++++++++++++++++++++++++++","isTableExists=true");
return true;
}
cursor.close();
}
Log.d("++++++++++++++++++++++++++++++++++++++","isTableExists=false");
return false;
}
}
7、因为要展示数据库里数据,数据库里面的数据属于多条数据,并要进行界面刷新来实时展示,所以要用到Adapter类,所以我建了一个自己使用的Adapter类,在packages/apps/Settings/src/com/android/settings/myceshi下新建了一个MyAdapter.java
package com.android.settings.myceshi;
import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivitySettingsManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AnnotationSpan;
import com.android.settingslib.CustomDialogPreferenceCompat;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.common.net.InternetDomainName;
import java.util.HashMap;
import java.util.Map;
import android.widget.Toast;
import android.widget.CompoundButton;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import java.util.ArrayList;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private String[] mDataset;
// 提供一个构造器来传入数据集
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
// 创建新的ViewHolder(在布局文件中定义的视图)
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
return new MyViewHolder(view);
}
// 绑定数据到ViewHolder
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(mDataset[position]);
}
// 返回数据的数量
@Override
public int getItemCount() {
return mDataset.length;
}
// 自定义的ViewHolder,持有每个Item的所有界面元素
public static class MyViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.text_view);
}
}
}
8、根据我前面写的my_settings_ceshi.xml,功能其一就是一个点击事件,功能其二是一个Switch开关,功能其三是一个单选按钮,功能其四是一个多选功能,网络黑名单在我另一篇文章中有介绍,最后的的软件黑名单则需要在packages/apps/Settings/src/com/android/settings/myceshi下新建一个PrivateDialogPreference4.java
package com.android.settings.myceshi;
import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivitySettingsManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AnnotationSpan;
import com.android.settingslib.CustomDialogPreferenceCompat;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.common.net.InternetDomainName;
import java.util.HashMap;
import java.util.Map;
import android.widget.Toast;
import android.widget.CompoundButton;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import java.util.ArrayList;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.database.sqlite.SQLiteDatabase;
import android.database.Cursor;
import android.net.ConnectivityManager;
// import com.android.server.DatabaseHelper;
import android.content.ContentValues;
import com.android.server.PackagesDatabaseHelper;
public class PrivateDialogPreference4 extends CustomDialogPreferenceCompat implements View.OnClickListener{
List<String> dataList = new ArrayList<>();
RecyclerView recyclerView;
MyAdapter adapter;
private String[] mDataset;
private EditText mEditText;
private Button Button1,Button2,Button3;
public PrivateDialogPreference4(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化设置
}
@Override
protected void onBindDialogView(View view) {
mEditText = view.findViewById(R.id.private_dns_mode_provider_hostname);
Button1 = view.findViewById(R.id.button1);
Button1.setOnClickListener(this);
Button2 = view.findViewById(R.id.button2);
Button2.setOnClickListener(this);
Button3 = view.findViewById(R.id.button3);
Button3.setOnClickListener(this);
recyclerView = view.findViewById(R.id.my_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
DataBase();
}
@Override
public void onClick(View v) {
int viewId = v.getId();
if (viewId == R.id.button1) {
Toast.makeText(getContext(), "点击了取消", Toast.LENGTH_SHORT).show();
// getContext().dismiss();
} else if (viewId == R.id.button2) {
Toast.makeText(getContext(), "点击了确认", Toast.LENGTH_SHORT).show();
if(!mEditText.getText().toString().isEmpty()){
if(isDataExists(mEditText.getText().toString())) {
// 数据存在
Log.d("----------------------------------------","!!!!!!!!cursor.moveToFirst()");
Toast.makeText(getContext(), "数据存在", Toast.LENGTH_SHORT).show();
}else{
// 数据不存在
//写入数据库
witeData(mEditText.getText().toString());
DataBase();
}
}else{
Toast.makeText(getContext(), "内容为空", Toast.LENGTH_SHORT).show();
Log.d("----------------------------------------","mEditText.getText().toString().isEmpty()");
}
}else if (viewId == R.id.button3){
Toast.makeText(getContext(), "点击了删除", Toast.LENGTH_SHORT).show();
if(!mEditText.getText().toString().isEmpty()){
if(isDataExists(mEditText.getText().toString())) {
// 数据存在
//删除数据
delData(mEditText.getText().toString());
Log.d("----------------------------------------","!!!!!!!!cursor.moveToFirst()");
DataBase();
}else{
// 数据不存在
Toast.makeText(getContext(), "数据不存在", Toast.LENGTH_SHORT).show();
Log.d("----------------------------------------","!!!!!!!!cursor.moveToFirst()!");
}
}else{
Toast.makeText(getContext(), "内容为空", Toast.LENGTH_SHORT).show();
Log.d("----------------------------------------","mEditText.getText().toString().isEmpty()");
}
}
}
//启动数据库
public void DataBase(){
PackagesDatabaseHelper dbHelper = new PackagesDatabaseHelper(getContext());
// 获取可读数据库
SQLiteDatabase db = dbHelper.getReadableDatabase();
// 执行查询
Cursor cursor = db.query("mytable", null, null, null, null, null, null);
// 创建一个List来保存数据
// List<String> dataList = new ArrayList<>();
// 遍历Cursor
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndexOrThrow("ID"));
Log.d("-------------------------------ID","---------------------------"+id);
String name = cursor.getString(cursor.getColumnIndexOrThrow("NAME"));
Log.d("-------------------------------NAME","---------------------------"+name);
// 创建数据模型并添加到List
dataList.add(name);
}
Log.d("-------------------------------DataBase","---------------------------"+dataList);
mDataset = dataList.toArray(new String[0]);
adapter = new MyAdapter(mDataset);
recyclerView.setAdapter(adapter);
dataList.clear();
// for(int i = 0; i<=dataList.size(); i++){
// DataBaseName[i]=dataList.get(i);
// }
// 关闭Cursor
cursor.close();
// 关闭数据库连接
db.close();
//刷新适配器
adapter.notifyDataSetChanged();
}
//数据库添加数据
public void witeData(String data){
PackagesDatabaseHelper dbHelper = new PackagesDatabaseHelper(getContext());
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("NAME", data); // 将column1替换为你的列名,value1替换为你想插入的值
db.insert("mytable", null, values);
db.close();
}
//数据库删除数据
public void delData(String data){
PackagesDatabaseHelper dbHelper = new PackagesDatabaseHelper(getContext());
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("mytable", "NAME = ?", new String[]{data});
db.close();
}
//判断数据库里是否存在已有的数据
public boolean isDataExists(String name){
PackagesDatabaseHelper dbHelper = new PackagesDatabaseHelper(getContext());
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query("mytable", null, "NAME = ?", new String[]{name}, null, null, null);
int cursorCount = cursor.getCount();
Log.d("++++++++++++++++++++++++++++",""+cursorCount);
if(cursor.moveToFirst()) {
// 数据存在
cursor.close();
db.close();
return true;
} else {
// 数据不存在
cursor.close();
db.close();
return false;
}
}
// //判断字符串里包含的 ":" 的个数
// public int countA(String str) {
// int count = 0;
// for (int i = 0; i < str.length(); i++) {
// if (str.charAt(i) == ':') {
// count++;
// }
// }
// return count;
// }
}
9、PrivateDialogPreference4.java布局文件需要在packages/apps/Settings/res/layout下新建一个my_dialog4.xml。
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
<EditText
android:id="@+id/private_dns_mode_provider_hostname"
android:hint="随便输入什么内容,仅测试使用"
style="@android:style/Widget.CompoundButton.RadioButton"
android:imeOptions="actionDone"
android:inputType="textFilter|textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginEnd="8dp"
android:minHeight="@dimen/developer_option_dialog_min_height"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确认"/>
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除"/>
</LinearLayout>
<include
android:id="@+id/private_dns_help_info"
layout="@layout/preference_widget_dialog_summary"/>
</LinearLayout>
</ScrollView>
最后大致流程就是这样了,感谢阅读。