Android:无法添加 window。此 window 类型的权限被拒绝

Android: Unable to add window. Permission denied for this window type

我正在开发一个应用程序,我需要在其中显示 window 一些信息 ON 锁定屏幕 (KeyGuard) 而不解锁 phone.我想我可以用 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG

但每次我的应用程序崩溃并出现以下错误时:

android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@40ec8528 -- permission denied for this window type

这些帖子(here, here and here)都给出了相同的答案。 在Manifest文件中添加以下权限。

android.permission.SYSTEM_ALERT_WINDOW

我已经实施了解决方案,但我仍然遇到同样的错误。 知道我做错了什么吗?

这是我的清单文件中的权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.droidpilot.keyguardwindow" >

<uses-sdk
    android:minSdkVersion="16"
    android:targetSdkVersion="21" />

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />

这是我用来将 Window 添加到锁定屏幕的代码

WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    LayoutInflater mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    mView = mInflater.inflate(R.layout.lock_screen_notif, null);

    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
            PixelFormat.TRANSLUCENT
    );

    wm.addView(mView, params);

有人知道吗?

P.S。我正在 HTC Desire 620 DS 运行 Android 4.4.2

上进行测试

出于显而易见的原因,普通应用程序不允许在锁定屏幕 上创建任意windows。如果我在你的锁屏上创建一个可以完美模仿真实锁屏的 window,你认为我能做什么?

您的错误的技术原因是使用了 TYPE_KEYGUARD_DIALOG 标志 - 它需要 android.permission.INTERNAL_SYSTEM_WINDOW 这是签名级别的权限。这意味着只有使用与权限创建者相同证书签名的应用才能使用它。

android.permission.INTERNAL_SYSTEM_WINDOW 的创建者是 Android 系统本身,因此除非您的应用程序是 OS 的一部分,否则您没有机会。

notifying the user of information from the lockscreen 有明确定义且有据可查的方法。您可以创建显示在锁屏上的自定义通知,用户可以与它们互动。

我确实成功地使用 TYPE_SYSTEM_OVERLAY 而不是 TYPE_KEYGUARD_DIALOG 在锁定屏幕上显示了 window。这按预期工作并在锁定屏幕上添加 window。

问题在于 window 被添加到 可能的所有内容之上。 也就是说,window 甚至会出现在在安全锁定屏幕的情况下,keypad/pattern 锁的顶部。在不安全锁屏的情况下,如果您从锁屏打开它,它会出现在通知托盘的顶部。

对我来说,这是不可接受的。我希望这可以帮助其他面临此问题的人。

我尽力尝试了这个问题的所有可用示例。终于我得到了答案我不知道它有多可靠但我的应用程序现在没有崩溃。

windowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
    //here is all the science of params
    final LayoutParams myParams = new LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            LayoutParams.TYPE_SYSTEM_ERROR,
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
            PixelFormat.TRANSLUCENT
    );

在您的清单文件中,只需授予权限

 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

除此之外,您还可以检查 API 级别,如果它 >=23 则

 if(Build.VERSION.SDK_INT >= 23) {
    if (!Settings.canDrawOverlays(Activity.this)) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse("package:" + getPackageName()));
        startActivityForResult(intent, 1234);
    }
}
            else
{
    Intent intent = new Intent(Activity.this, Service.class);
    startService(intent);
}

我希望它能帮助某个地方的人。 完整示例 https://anam-android-codes.blogspot.in/?m=1

如果您使用apiLevel >= 19,请不要使用

WindowManager.LayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 

出现以下错误:

android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@40ec8528 -- permission denied for this window type

改用这个:

LayoutParams.TYPE_TOAST or TYPE_APPLICATION_PANEL

我想你应该区分目标(奥利奥之前和之后)

int LAYOUT_FLAG;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;
}

params = new WindowManager.LayoutParams(
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    LAYOUT_FLAG,
    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
    PixelFormat.TRANSLUCENT);

对于Android API 8.0.0 级别,您应该使用

WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

而不是

LayoutParams.TYPE_TOAST or TYPE_APPLICATION_PANEL

SYSTEM_ALERT.

搜索

Draw over other apps

在您的设置中启用您的应用。 对于 Android 8 奥利奥,尝试

Settings > Apps & Notifications > App info > Display over other apps > Enable

将项目中的 Windowmanger 标志 "TYPE_SYSTEM_OVERLAY" 更改为 "TYPE_APPLICATION_OVERLAY" 以与 Android O

兼容

WindowManager.LayoutParams.TYPE_PHONEWindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

LayoutParams.TYPE_PHONE 已弃用。我已经像这样更新了我的代码。

parameters = if (Build.VERSION.SDK_INT > 25) {
        LayoutParams(
                minHW * 2 / 3, minHW * 2 / 3,
                LayoutParams.TYPE_APPLICATION_OVERLAY,
                LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT)
    }else {
        LayoutParams(
                minHW * 2 / 3, minHW * 2 / 3,
                LayoutParams.TYPE_PHONE,
                LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT)
    }

LayoutParams.TYPE_APPLICATION_OVERLAY 需要 api 26 级或以上。

试试这个代码是否完美

int layout_parms;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 

    {  
         layout_parms = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

    }

     else {

            layout_parms = WindowManager.LayoutParams.TYPE_PHONE;

    }

    yourparams = new WindowManager.LayoutParams(       
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            layout_parms,
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT);

首先你要确保你在清单文件中有添加权限。

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

查看应用是否有借鉴其他应用权限? 此权限默认适用于 API<23。但是对于 API > 23 你必须在运行时请求权限。

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {

    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
            Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, 1);
} 

使用此代码:

public class ChatHeadService extends Service {

private WindowManager mWindowManager;
private View mChatHeadView;

WindowManager.LayoutParams params;

public ChatHeadService() {
}

@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();

    Language language = new Language();
    //Inflate the chat head layout we created
    mChatHeadView = LayoutInflater.from(this).inflate(R.layout.dialog_incoming_call, null);


    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.TRANSLUCENT);

        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
        params.x = 0;
        params.y = 100;
        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        mWindowManager.addView(mChatHeadView, params);

    } else {
        params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.TRANSLUCENT);


        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
        params.x = 0;
        params.y = 100;
        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        mWindowManager.addView(mChatHeadView, params);
    }

    TextView tvTitle=mChatHeadView.findViewById(R.id.tvTitle);
    tvTitle.setText("Incoming Call");

    //Set the close button.
    Button btnReject = (Button) mChatHeadView.findViewById(R.id.btnReject);
    btnReject.setText(language.getText(R.string.reject));
    btnReject.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //close the service and remove the chat head from the window
            stopSelf();
        }
    });

    //Drag and move chat head using user's touch action.
    final Button btnAccept = (Button) mChatHeadView.findViewById(R.id.btnAccept);
    btnAccept.setText(language.getText(R.string.accept));


    LinearLayout linearLayoutMain=mChatHeadView.findViewById(R.id.linearLayoutMain);



    linearLayoutMain.setOnTouchListener(new View.OnTouchListener() {
        private int lastAction;
        private int initialX;
        private int initialY;
        private float initialTouchX;
        private float initialTouchY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:

                    //remember the initial position.
                    initialX = params.x;
                    initialY = params.y;

                    //get the touch location
                    initialTouchX = event.getRawX();
                    initialTouchY = event.getRawY();

                    lastAction = event.getAction();
                    return true;
                case MotionEvent.ACTION_UP:
                    //As we implemented on touch listener with ACTION_MOVE,
                    //we have to check if the previous action was ACTION_DOWN
                    //to identify if the user clicked the view or not.
                    if (lastAction == MotionEvent.ACTION_DOWN) {
                        //Open the chat conversation click.
                        Intent intent = new Intent(ChatHeadService.this, HomeActivity.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);

                        //close the service and remove the chat heads
                        stopSelf();
                    }
                    lastAction = event.getAction();
                    return true;
                case MotionEvent.ACTION_MOVE:
                    //Calculate the X and Y coordinates of the view.
                    params.x = initialX + (int) (event.getRawX() - initialTouchX);
                    params.y = initialY + (int) (event.getRawY() - initialTouchY);

                    //Update the layout with new X & Y coordinate
                    mWindowManager.updateViewLayout(mChatHeadView, params);
                    lastAction = event.getAction();
                    return true;
            }
            return false;
        }
    });
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (mChatHeadView != null) mWindowManager.removeView(mChatHeadView);
}

}

拒绝权限的主要原因是我们没有绘制其他应用程序的权限,我们必须提供可以通过以下代码完成的绘制其他应用程序的权限

权限请求代码

    public static int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE = 5469;

将此添加到您的 MainActivity


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            askPermission();
}


private void askPermission() {
        Intent intent= new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:"+getPackageName()));
        startActivityForResult(intent,ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
}

也添加这个

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE){
            if(!Settings.canDrawOverlays(this)){
                askPermission();
            }
        }
    }

我努力寻找 ApplicationContextTYPE_SYSTEM_ALERT 的工作解决方案,但发现了令人困惑的解决方案,如果您希望从任何 activity 打开对话框,即使对话框是单例你必须使用 getApplicationContext(),如果想要对话框应该是 TYPE_SYSTEM_ALERT 你将需要以下步骤:

首先获取具有正确主题的对话框实例,您还需要像我在以下代码片段中所做的那样管理版本兼容性:

AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext(), R.style.Theme_AppCompat_Light);

设置标题、消息和按钮后,您必须将对话框构建为:

AlertDialog alert = builder.create();

现在type在这里起主要作用,因为这是崩溃的原因,我做了如下兼容性处理:

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        alert.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY - 1);

    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    }

注意:如果您使用带有 AppCompatDialog 的自定义对话框,如下所示:

AppCompatDialog dialog = new AppCompatDialog(getApplicationContext(), R.style.Theme_AppCompat_Light);

您可以直接将类型定义为 AppCompatDialog 实例,如下所示:

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY - 1);

    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    }

不要忘记添加清单权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

我刚刚通过以下步骤向 WindowManager 添加了简单视图:

  1. 创建一个要显示的布局文件(dummy_layout 在我的例子中)
  2. 在清单中动态添加权限。
// 1. Show view
private void showCustomPopupMenu()
{
    windowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
    // LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    // View view = layoutInflater.inflate(R.layout.dummy_layout, null);
    ViewGroup valetModeWindow = (ViewGroup) View.inflate(this, R.layout.dummy_layout, null);
    int LAYOUT_FLAG;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
        LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;
    }
    WindowManager.LayoutParams params=new WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        LAYOUT_FLAG,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        PixelFormat.TRANSLUCENT);

    params.gravity= Gravity.CENTER|Gravity.CENTER;
    params.x=0;
    params.y=0;
    windowManager.addView(valetModeWindow, params);
}

// 2. Get permissions by asking
if (!Settings.canDrawOverlays(this)) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, 1234);
}

有了它,您可以向 WM 添加视图。

在 Pie 中测试。

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_PHONE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
            PixelFormat.TRANSLUCENT);

    params.gravity = Gravity.START | Gravity.TOP;
    params.x = left;
    params.y = top;
    windowManager.addView(view, params);

} else {
    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
            PixelFormat.TRANSLUCENT);


    params.gravity = Gravity.START | Gravity.TOP;
    params.x = left;
    params.y = top;
    windowManager.addView(view, params);
}

确保为 OS 版本添加了 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR < Oreo,因为它已被弃用。