android12 通过adb命令安装apk的黑名单

实现目标: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>

最后大致流程就是这样了,感谢阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值