You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Android TvSettings应用菜单添加PIN码验证及设备校验功能

给LineageOS TvSettings添加PIN验证与设备校验功能

一、设备校验逻辑实现

通过读取build.prop中的ro.product.model字段,判断当前设备是否为授权设备,非授权设备强制触发PIN验证。

import android.os.SystemProperties;

// 判断当前设备是否为授权设备
private boolean isAuthorizedDevice() {
    // 替换为你的目标设备型号
    String TARGET_DEVICE_MODEL = "SampleTVModel";
    String currentDeviceModel = SystemProperties.get("ro.product.model", "");
    return TARGET_DEVICE_MODEL.equals(currentDeviceModel);
}

二、数字键盘PIN验证弹窗实现

自定义适配TV端的数字键盘弹窗,实现PIN码输入、验证逻辑:

1. 弹窗布局(res/layout/dialog_pin_verify.xml)

<?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="wrap_content"
    android:orientation="vertical"
    android:padding="24dp">

    <TextView
        android:id="@+id/pin_input_tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请输入PIN码"
        android:textSize="20sp"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:id="@+id/pin_input"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginVertical="16dp"
        android:textSize="24sp"
        android:inputType="numberPassword"
        android:layout_gravity="center_horizontal"/>

    <!-- 数字键盘区域 -->
    <GridLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:columnCount="3"
        android:rowCount="4">

        <Button
            android:id="@+id/btn_num_1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="1"/>

        <Button
            android:id="@+id/btn_num_2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="2"/>

        <Button
            android:id="@+id/btn_num_3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="3"/>

        <Button
            android:id="@+id/btn_num_4"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="4"/>

        <Button
            android:id="@+id/btn_num_5"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="5"/>

        <Button
            android:id="@+id/btn_num_6"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="6"/>

        <Button
            android:id="@+id/btn_num_7"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="7"/>

        <Button
            android:id="@+id/btn_num_8"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="8"/>

        <Button
            android:id="@+id/btn_num_9"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="9"/>

        <View
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"/>

        <Button
            android:id="@+id/btn_num_0"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="0"/>

        <Button
            android:id="@+id/btn_delete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_columnWeight="1"
            android:text="删除"/>
    </GridLayout>
</LinearLayout>

2. 弹窗逻辑实现

import android.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

// 显示PIN验证弹窗
private void showPinVerifyDialog(final OnPinVerifyCallback callback) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
    View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_pin_verify, null);
    TextView pinInputTv = dialogView.findViewById(R.id.pin_input);

    // 数字按钮点击事件
    dialogView.findViewById(R.id.btn_num_1).setOnClickListener(v -> pinInputTv.append("1"));
    dialogView.findViewById(R.id.btn_num_2).setOnClickListener(v -> pinInputTv.append("2"));
    dialogView.findViewById(R.id.btn_num_3).setOnClickListener(v -> pinInputTv.append("3"));
    dialogView.findViewById(R.id.btn_num_4).setOnClickListener(v -> pinInputTv.append("4"));
    dialogView.findViewById(R.id.btn_num_5).setOnClickListener(v -> pinInputTv.append("5"));
    dialogView.findViewById(R.id.btn_num_6).setOnClickListener(v -> pinInputTv.append("6"));
    dialogView.findViewById(R.id.btn_num_7).setOnClickListener(v -> pinInputTv.append("7"));
    dialogView.findViewById(R.id.btn_num_8).setOnClickListener(v -> pinInputTv.append("8"));
    dialogView.findViewById(R.id.btn_num_9).setOnClickListener(v -> pinInputTv.append("9"));
    dialogView.findViewById(R.id.btn_num_0).setOnClickListener(v -> pinInputTv.append("0"));

    // 删除按钮逻辑
    dialogView.findViewById(R.id.btn_delete).setOnClickListener(v -> {
        String currentText = pinInputTv.getText().toString();
        if (!currentText.isEmpty()) {
            pinInputTv.setText(currentText.substring(0, currentText.length() - 1));
        }
    });

    builder.setView(dialogView)
           .setPositiveButton("确认", (dialog, which) -> {
               String inputPin = pinInputTv.getText().toString().trim();
               // 替换为你的预设PIN码,建议放在strings.xml中
               if ("1234".equals(inputPin)) {
                   callback.onSuccess();
               } else {
                   Toast.makeText(getContext(), "PIN码错误", Toast.LENGTH_SHORT).show();
                   callback.onFailed();
               }
           })
           .setNegativeButton("取消", (dialog, which) -> callback.onCancel())
           .create().show();
}

// PIN验证回调接口
public interface OnPinVerifyCallback {
    void onSuccess();
    void onFailed();
    void onCancel();
}

三、菜单点击拦截与验证触发

在TvSettings对应的Fragment中,重写onPreferenceTreeClick方法,拦截目标菜单(显示、应用、开发者选项)的点击事件,触发验证逻辑:

import android.preference.Preference;
import android.preference.PreferenceScreen;

@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
    String preferenceKey = preference.getKey();
    // 定义需要验证的菜单key,根据实际xml中的key调整
    String[] protectedKeys = {"display_settings", "apps_settings", "developer_options"};

    for (String key : protectedKeys) {
        if (key.equals(preferenceKey)) {
            // 判断是否需要触发验证:非授权设备强制验证,授权设备可按需调整
            if (!isAuthorizedDevice()) {
                showPinVerifyDialog(new OnPinVerifyCallback() {
                    @Override
                    public void onSuccess() {
                        // 验证通过,执行原跳转逻辑
                        TvSettingsFragment.super.onPreferenceTreeClick(preferenceScreen, preference);
                    }

                    @Override
                    public void onFailed() {
                        // 验证失败,不执行跳转
                    }

                    @Override
                    public void onCancel() {
                        // 取消验证,不执行跳转
                    }
                });
                return true; // 拦截原点击事件
            } else {
                // 授权设备也需要验证的话,复制上面的showPinVerifyDialog逻辑即可
                // 示例:授权设备跳过验证,直接跳转
                return super.onPreferenceTreeClick(preferenceScreen, preference);
            }
        }
    }
    return super.onPreferenceTreeClick(preferenceScreen, preference);
}

四、关键注意事项

  • PIN码配置:不要硬编码PIN码,建议将预设PIN放在res/values/strings.xml中,方便后续修改。
  • TV端适配:确保数字键盘弹窗的按钮支持遥控器焦点导航,可通过android:focusable="true"等属性优化。
  • 开发者选项适配:开发者选项默认隐藏,需确保在用户开启开发者选项后,拦截逻辑依然生效。
  • 权限说明:读取ro.product.model无需额外权限,直接通过SystemProperties调用即可。

内容的提问来源于stack exchange,提问作者Cobra91

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 21:10:59