# mini-permission 分支改动说明

在 `mini-change` 分支基础上，进一步优化权限申请流程：**按需权限申请** + **避免 PermissionRequest 泄露**。

## 改动概览

| 文件 | 改动 |
|------|------|
| `H5FaceWebChromeClient.java` | `onPermissionRequest` 按 resources 动态映射权限；所有分支必 grant/deny；新增 `denyPermissionRequest()`；`enterTrtcFaceVerify` 简化 |
| `MainActivity.java` | 新增 `checkPermissionsGranted()` / `requestFacePermissions()`；拒绝分支增加 `denyPermissionRequest()` 调用 |

---

## 1. H5FaceWebChromeClient.onPermissionRequest — 按需权限申请

H5 端 `getUserMedia` 请求的资源由 `PermissionRequest.getResources()` 给出，动态映射系统权限：

- `PermissionRequest.RESOURCE_VIDEO_CAPTURE` → `CAMERA`
- `PermissionRequest.RESOURCE_AUDIO_CAPTURE` → `RECORD_AUDIO`

**关键原则**：所有分支必须有 `grant()` 或 `deny()` 出口，避免 H5 端 Promise 永远挂起。

```java
@Override
public void onPermissionRequest(PermissionRequest request) {
    if (request == null) return;
    final PermissionRequest req = request;
    String origin = req.getOrigin() != null ? req.getOrigin().toString() : "null";
    Log.d(TAG, "onPermissionRequest origin=" + origin);

    if (activity == null) {
        req.deny();
        return;
    }

    // 按 H5 请求的资源动态映射系统权限
    java.util.List<String> needPerms = new java.util.ArrayList<>();
    if (req.getResources() != null) {
        for (String res : req.getResources()) {
            if (PermissionRequest.RESOURCE_VIDEO_CAPTURE.equals(res)) {
                needPerms.add(android.Manifest.permission.CAMERA);
            } else if (PermissionRequest.RESOURCE_AUDIO_CAPTURE.equals(res)) {
                needPerms.add(android.Manifest.permission.RECORD_AUDIO);
            }
        }
    }
    if (needPerms.isEmpty()) {
        req.deny();
        return;
    }

    if (activity.checkPermissionsGranted(needPerms)) {
        // 权限已有，同步 grant
        req.grant(req.getResources());
    } else {
        // 权限缺失，异步申请
        this.request = req;
        activity.requestFacePermissions(needPerms.toArray(new String[0]));
    }
}
```

### 优点

- 不硬编码申请相机+麦克风，H5 只请求视频就不申请麦克风。
- 权限已有**同步 grant**，不覆盖 `this.request` 字段。
- 每个分支都 grant 或 deny，H5 端不会卡死。

## 2. 新增 H5FaceWebChromeClient.denyPermissionRequest()

权限被拒绝时主动 deny H5 的 PermissionRequest，确保 `getUserMedia` Promise 收到 rejection 回调。

```java
public void denyPermissionRequest() {
    if (request != null) {
        request.deny();
        request = null;
    }
}
```

在 `MainActivity.onRequestPermissionsResult` 的 TRTC 拒绝分支调用：

```java
if (deniedForever) {
    webViewClient.denyPermissionRequest();  // 新增
    openAppDetail(PERMISSION_QUEST_TRTC_CAMERA_VERIFY);
} else {
    webViewClient.denyPermissionRequest();  // 新增
    askPermissionError();
}
```

## 3. H5FaceWebChromeClient.enterTrtcFaceVerify 简化

去掉二次域名判断，直接 grant，grant 后置空 `this.request` 避免残留。

```java
public void enterTrtcFaceVerify() {
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
        if (request != null) {
            request.grant(request.getResources());
            this.request = null; // grant 后置空，避免泄露
        }
    }
}
```

## 4. MainActivity 新增方法

```java
// 批量检查权限，供 onPermissionRequest 同步判断
public boolean checkPermissionsGranted(java.util.List<String> permissions) {
    for (String p : permissions) {
        if (checkSdkPermission(p) != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}

// 异步申请权限
public void requestFacePermissions(String[] permissions) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        ActivityCompat.requestPermissions(this, permissions, PERMISSION_QUEST_TRTC_CAMERA_VERIFY);
    } else {
        openAppDetail(PERMISSION_QUEST_TRTC_CAMERA_VERIFY);
    }
}
```

## 与 mini-change 的差异总结

| 方面 | mini-change | mini-permission |
|------|-------------|-----------------|
| 权限申请方式 | 硬编码 trtc=true，固定申请相机+麦克风 | 按 H5 resources 动态映射 |
| PermissionRequest 生命周期 | 只赋值，不置空 | grant/deny 后置空，避免泄露 |
| 拒绝时通知 H5 | 不 deny，getUserMedia 卡死 | 主动 deny，H5 收到回调 |
| 权限已有 | 走异步流程，覆盖 this.request | 同步 grant，不覆盖 |
| 域名判断 | onPermissionRequest + enterTrtcFaceVerify 双重检查 | 不做域名白名单，直接处理所有请求 |
