Android系统默认赋予浏览器权限以及Android恶意覆盖导致谷歌浏览器授权失败的解决办法
一、Android系统默认赋予浏览器权限
只要是设计到默认赋权,就在framework下找这个类:base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
安卓系统开机的时候会调用这个类里面的下面这个方法:
public void grantDefaultPermissions(int userId) {
DelayingPackageManagerCache pm = new DelayingPackageManagerCache();
grantPermissionsToSysComponentsAndPrivApps(pm, userId);
grantDefaultSystemHandlerPermissions(pm, userId);
grantSignatureAppsNotificationPermissions(pm, userId);
grantDefaultPermissionExceptions(pm, userId);
// Apply delayed state
pm.apply();
}
grantDefaultSystemHandlerPermissions方法里面会默认赋予浏览器权限
private void grantDefaultSystemHandlerPermissions(PackageManagerWrapper pm, int userId) {
......
// Browser
String browserPackage = ArrayUtils.firstOrNull(getKnownPackages(
KnownPackages.PACKAGE_BROWSER, userId));
if (browserPackage == null) {
browserPackage = getDefaultSystemHandlerActivityPackageForCategory(pm,
Intent.CATEGORY_APP_BROWSER, userId);
if (!pm.isSystemPackage(browserPackage)) {
browserPackage = null;
}
}
grantPermissionsToPackage(pm, browserPackage, userId, false /* ignoreSystemPackage */,
true /*whitelistRestrictedPermissions*/, FOREGROUND_LOCATION_PERMISSIONS);
}
代码中通过"Intent.CATEGORY_APP_BROWSER"这个标识去判断是否是浏览器应用,如果是浏览器应用将会通过grantPermissionsToPackage方法去赋予浏览器权限
@SafeVarargs
private final void grantPermissionsToPackage(PackageManagerWrapper pm, String packageName,
int userId, boolean ignoreSystemPackage, boolean whitelistRestrictedPermissions,
Set<String>... permissionGroups) {
grantPermissionsToPackage(pm, pm.getPackageInfo(packageName),
userId, false /* systemFixed */, ignoreSystemPackage,
whitelistRestrictedPermissions, permissionGroups);
}
通过代码可以看到permissionGroups是一个可变参数,如果想给浏览器添加更多的默认参数只要在后面添加相应的权限即可,如下:
grantPermissionsToPackage(pm, browserPackage, userId, false /* ignoreSystemPackage */,
true /*whitelistRestrictedPermissions*/, FOREGROUND_LOCATION_PERMISSIONS,NOTIFICATION_PERMISSIONS,STORAGE_PERMISSIONS,CAMERA_PERMISSIONS,MICROPHONE_PERMISSIONS);
二、Android恶意覆盖导致谷歌浏览器授权失败的解决办法
在通过谷歌浏览器使用zoom会议的过程中我们发现,当授予zoom会议摄像头或者麦克风权限的时候,点击确认授予权限时会弹出以下弹窗导致授权失败,从而导致zoom会议无法打开摄像头和麦克风。
首先我们借助AI工具看一下谷歌浏览器的源码,“此网站无法请求授权,请关闭来着其他应用的所有气泡和叠加层,然后重试”这个弹窗是什么条件下触发的,得到以下信息
谷歌浏览器(Chromium)中关于 “此网站无法请求授权” 错误提示的源码主要涉及权限请求拦截、覆盖层检测和用户界面通知三个核心模块。以下从源码层面解析其实现逻辑:
一、错误提示的触发条件
该错误通常在以下情况触发(基于content/browser/permission_manager
模块):
- 存在屏幕覆盖层(如悬浮窗、输入法面板等)
- 权限请求被系统层拦截
- 渲染进程与浏览器进程通信中断
- 安全策略阻止跨域授权请求
二、核心源码分析
1. 覆盖层检测机制(Overlay Detector)
源码路径:content/browser/android/overlay_detector_android.cc
// content/browser/android/overlay_detector_android.cc
bool OverlayDetector::IsOverlayPresent() {
// 获取系统窗口管理器服务
WindowManager* wm = WindowManager::GetInstance();
// 检查是否存在非系统级覆盖窗口
std::vector<WindowInfo> windows = wm->GetAllWindows();
for (const auto& window : windows) {
if (!window.is_system_window &&
window.overlaps_application_area) {
return true; // 存在恶意覆盖层
}
}
return false;
}
2. 权限请求拦截逻辑
源码路径:content/browser/permission_manager/permission_manager_impl.cc
// content/browser/permission_manager/permission_manager_impl.cc
void PermissionManagerImpl::RequestPermission(
PermissionType permission,
RenderFrameHost* render_frame_host,
const GURL& requesting_origin,
bool user_gesture,
base::OnceCallback<void(PermissionStatus)> callback) {
// 检查是否存在覆盖层
if (overlay_detector_->IsOverlayPresent()) {
// 触发覆盖层错误提示
NotifyOverlayError(render_frame_host, requesting_origin);
std::move(callback).Run(PERMISSION_STATUS_DENIED);
return;
}
// 其他权限校验逻辑...
}
void PermissionManagerImpl::NotifyOverlayError(
RenderFrameHost* render_frame_host,
const GURL& origin) {
// 创建错误消息
std::string error_message =
"此网站无法请求授权,请关闭来自其他应用的所有气泡和叠加层,然后重试";
// 通过WebUI发送错误通知到渲染进程
WebUIMessageHandler::Send(
render_frame_host,
"chrome://overlay-error/",
base::Value(error_message));
}
3. 用户界面通知实现
源码路径:chrome/browser/ui/android/overlay_error_dialog.cc
// chrome/browser/ui/android/overlay_error_dialog.java
public class OverlayErrorDialog {
private final Activity activity;
private final String origin;
private AlertDialog dialog;
public OverlayErrorDialog(Activity activity, String origin) {
this.activity = activity;
this.origin = origin;
}
public void show() {
// 构建错误提示对话框
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("授权失败")
.setMessage("此网站无法请求授权,请关闭来自其他应用的所有气泡和叠加层,然后重试")
.setPositiveButton("确定", (dialog, which) -> dialog.dismiss())
.setIcon(R.drawable.ic_warning);
dialog = builder.create();
dialog.show();
}
public void dismiss() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
}
4. 系统权限交互逻辑
源码路径:content/browser/android/permission_request_controller_android.cc
// content/browser/android/permission_request_controller_android.cc
void PermissionRequestController::OnSystemPermissionsResult(
int request_code,
const std::vector<std::string>& permissions,
const std::vector<int>& grant_results) {
// 处理系统权限请求结果
if (grant_results.empty() || grant_results[0] != PackageManager.PERMISSION_GRANTED) {
// 权限被拒绝,检查是否因覆盖层导致
if (overlay_detector_->IsOverlayPresent()) {
ShowOverlayErrorDialog();
} else {
ShowPermissionDeniedDialog();
}
}
}
三、错误提示的显示流程
- 检测阶段:当网站请求敏感权限(如摄像头、位置)时,浏览器通过
OverlayDetector
检查屏幕上是否存在可疑覆盖层。 - 拦截阶段:若检测到覆盖层,
PermissionManager
会拦截权限请求,并调用NotifyOverlayError
生成错误消息。 - 显示阶段:
OverlayErrorDialog
将错误消息包装为系统对话框,并通过 Android UI 框架显示给用户。
四、相关配置与扩展点
- 覆盖层检测阈值:可通过
chrome://flags
中的overlay-detection-sensitivity
标志调整检测灵敏度。 - 错误消息本地化:字符串资源定义在
chrome/android/java/res/values/strings.xml
中。 - 白名单机制:在
content/public/common/overlay_whitelist.h
中可配置允许的覆盖层应用。
五、解决思路与源码关联
当遇到 “此网站无法请求授权” 错误时,可从以下源码角度排查:
- 覆盖层检测失败:检查
OverlayDetector::IsOverlayPresent()
的返回逻辑。 - 权限请求流程中断:调试
PermissionManagerImpl::RequestPermission()
的执行路径。 - UI 显示异常:检查
OverlayErrorDialog
的生命周期管理。
再通过AI工具检查一下OverlayDetector::IsOverlayPresent()方法的返回逻辑,得到以下信息
在 Chromium 源码中,OverlayDetector::IsOverlayPresent()
方法用于检测屏幕上是否存在可能干扰浏览器权限请求的覆盖层(如悬浮窗、恶意叠加层等)。其返回逻辑涉及系统窗口管理、应用权限检查和窗口属性分析,以下是核心实现解析:
一、方法核心逻辑
源码路径:content/browser/android/overlay_detector_android.cc
bool OverlayDetector::IsOverlayPresent() {
// 1. 获取系统窗口管理器服务
WindowManager* wm = WindowManager::GetInstance();
if (!wm)
return false;
// 2. 获取所有可见窗口列表
std::vector<WindowInfo> windows = wm->GetVisibleWindows();
// 3. 检查每个窗口是否为可疑覆盖层
for (const auto& window : windows) {
// 跳过系统信任的窗口类型
if (IsSystemTrustedWindow(window))
continue;
// 检查窗口是否与浏览器内容区域重叠
if (window.overlaps_browser_content) {
// 检查窗口是否具有高风险属性
if (IsHighRiskOverlay(window)) {
LOG(WARNING) << "检测到高风险覆盖层: " << window.title;
return true;
}
// 累积非系统窗口计数
non_system_window_count_++;
}
}
// 4. 判断非系统窗口数量是否超过阈值
if (non_system_window_count_ > kMaxAllowedNonSystemWindows) {
LOG(WARNING) << "非系统覆盖层数量过多: " << non_system_window_count_;
return true;
}
return false;
}
二、关键辅助方法解析
1. 系统信任窗口判断
bool OverlayDetector::IsSystemTrustedWindow(const WindowInfo& window) {
// 系统状态栏、导航栏等属于信任窗口
if (window.type == WINDOW_TYPE_STATUS_BAR ||
window.type == WINDOW_TYPE_NAVIGATION_BAR) {
return true;
}
// 输入法窗口属于信任窗口(但需特殊处理)
if (window.type == WINDOW_TYPE_INPUT_METHOD) {
return is_keyboard_trusted_;
}
// 检查窗口是否由系统签名应用创建
if (window.is_system_signed) {
// 部分系统应用仍需进一步验证
if (IsSystemAppWithOverlayPermission(window.package_name)) {
return true;
}
}
return false;
}
2. 高风险覆盖层判断
bool OverlayDetector::IsHighRiskOverlay(const WindowInfo& window) {
// 检查窗口是否为"TYPE_APPLICATION_OVERLAY"类型(Android 8.0+的悬浮窗类型)
if (window.type == WINDOW_TYPE_APPLICATION_OVERLAY) {
// 检查窗口是否请求了危险权限
if (HasDangerousPermissions(window.package_name)) {
return true;
}
// 检查窗口是否来自已知风险应用
if (IsKnownRiskyApp(window.package_name)) {
return true;
}
}
// 检查窗口是否具有不透明全屏特性
if (window.is_opaque && window.covers_entire_screen) {
return true;
}
return false;
}
3. 窗口与浏览器内容区域重叠判断
bool OverlayDetector::DoesWindowOverlapBrowserContent(
const WindowInfo& window) {
// 获取浏览器内容区域坐标
gfx::Rect content_area = GetBrowserContentArea();
// 检查窗口边界是否与内容区域相交
return window.bounds.Intersects(content_area);
}
从以上的源码我们可以看到,如果是系统信任的窗口则直接跳过返回false,如果是窗口与浏览器窗口叠加并且是被判断为窗口具有高风险属性则返回true,弹窗就会弹出。这时候可以判断是系统的某个应用,覆盖在了浏览器的上层或者和浏览器在同一层级,并且具有高风险属性。通过代码排查,以下应用会一直显示在上层。打开谷歌浏览器时截屏并未打开,那只可能是侧边栏的问题了,这时候我们去设置菜单里面设置一下把侧边栏给隐藏掉,这时候再去打开zoom会议授权,发现授权成功了,弹窗没有弹出来,我们可以确定该问题是侧边栏导致的,查看侧边栏源码发现侧边栏使用TYPE_APPLICATION_OVERLAY
类型并且请求了危险权限,这下就解释得通了。
接下来要怎么处理该问题,最好的办法就是看一下能不能把侧边栏设置为系统信任的窗口,再次使用AI我们得到了以下解决办法。
申请系统签名
- 通过 OEM 合作或设备定制,为应用获取系统签名,从而使用
PRIVATE_FLAG_TRUSTED_OVERLAY
标志:// 系统应用才能生效的代码 if (isSystemApp()) { params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; }
- 通过 OEM 合作或设备定制,为应用获取系统签名,从而使用
加入设备白名单
与设备厂商合作,将应用添加到浏览器的信任白名单中:
// 设备特定配置(非公开API) chrome://flags/#overlay-allowed-apps=com.your.app
显然第一种方法是比较行得通的,侧边栏已经是系统应用,我们只需要添加PRIVATE_FLAG_TRUSTED_OVERLAY标志位即可,修改如下,即可解决恶意覆盖导致授权失败问题,该问题最后修改的地方比较简单只有一行,但是问题的处理和排查过程会相对麻烦一些,这也是大部分系统级问题的通病。其他应用(如悬浮球等应用的解决思路也是一样的),只不过该属性在安卓frameworks中是隐藏的,在悬浮球等应用中需要通过反射调用,这方面我们就不去讲解了。