在运行 Oreo 和 Pie 的设备中如何在锁定屏幕上覆盖布局

Posted

技术标签:

【中文标题】在运行 Oreo 和 Pie 的设备中如何在锁定屏幕上覆盖布局【英文标题】:How to overlay a layout on lock screen when in devices running Oreo and Pie 【发布时间】:2019-12-09 15:16:53 【问题描述】:

在我的应用程序中,当我运行服务时,我尝试在此处使用服务覆盖布局,当目标设备低于 oreo 但我尝试覆盖与 oreo 之后的版本相同,它不起作用但是当我按照其他答案中的建议将标志更改为 Type_Application_Overlay 时,它只会在屏幕解锁时显示布局,但我想在锁定屏幕上显示它我尝试了很多搜索但没有找到任何解决此问题的有用答案一些答案建议在锁屏上显示活动我也尝试过,但仍然没有任何帮助!

此外, 此外,Play 商店中有一些应用程序可以轻松地在锁定屏幕上显示任何布局,即使在 oreo 和 pie 上也是如此:

你可以看看这个应用程序:

https://play.google.com/store/apps/details?id=com.wifihacker.whousemywifi.wifirouter.wifisecurity

即使不要求任何覆盖权限或设备管理员权限,此应用也可以轻松地在锁屏上显示自定义全屏布局。

如果这个应用程序能够覆盖锁定屏幕,为什么我不能这样做?

对此的任何帮助和建议将不胜感激!

这是我目前的锁屏服务代码:

class LockScreenService : Service() 

    private lateinit var mReceiver: BroadcastReceiver
    private var isShowing = false

    private lateinit var windowManager: WindowManager
    lateinit var  params: WindowManager.LayoutParams
    lateinit var myview: View
    var  downX:Int = 0
    lateinit var locklayout:RelativeLayout
     var upX:Int = 0
    var indicator: WaveLoadingView?=null
    lateinit var date:TextView
    lateinit var time:TextView
    lateinit var settings:ImageView
    lateinit var unlock:LinearLayout
    var r:Runnable?=null
    companion object

    
    private val mBatInfoReceiver = object : BroadcastReceiver() 
        override fun onReceive(ctxt: Context, intent: Intent) 
            val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
            if (indicator!=null)
            
                indicator!!.progressValue = level
                indicator!!.setAnimDuration(3000)
                indicator!!.startAnimation()
            
            
    

    override fun onBind(intent: Intent): IBinder? 
        // TODO Auto-generated method stub
        return null
    

    override fun onCreate() 
        super.onCreate()
        try 
            EventBus.getDefault().register(this)
            windowManager = applicationContext.getSystemService(WINDOW_SERVICE) as WindowManager

            val li = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

            myview = li.inflate(R.layout.lockscreenlayout, null)
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) 
                params = WindowManager.LayoutParams(
                    WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                    PixelFormat.TRANSLUCENT
                )
             else 
                params = WindowManager.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
                    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                            or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                            or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                            or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                    PixelFormat.TRANSLUCENT
                )
                myview.setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            or View.SYSTEM_UI_FLAG_FULLSCREEN
                            or View.SYSTEM_UI_FLAG_VISIBLE
                            or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            or View.SYSTEM_UI_FLAG_IMMERSIVE
                            or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                )
            
            myview.setVisibility(View.VISIBLE)
            settings = myview.findViewById(R.id.imgsetting)
            settings.setOnClickListener 
                var intent = Intent(this, Settings::class.java)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                this.startActivity(intent)
                windowManager.removeViewImmediate(myview)
            

             indicator = myview.findViewById(R.id.indicator)
            locklayout = myview.findViewById(R.id.locklay)
            this.registerReceiver(this.mBatInfoReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            time = myview.findViewById(R.id.time)
            date = myview.findViewById(R.id.date)

            date.text =
                SimpleDateFormat("EEEE").format(Calendar.getInstance().time).toString() + "," + SimpleDateFormat("dd").format(
                    Calendar.getInstance().time
                ).toString() + " " + SimpleDateFormat("MMMM").format(Calendar.getInstance().time).toString()
            try 
                unlock.setOnTouchListener(object : View.OnTouchListener 
                    override fun onTouch(v: View?, event: MotionEvent?): Boolean 
                        if (event!!.getAction() == MotionEvent.ACTION_DOWN) 
                            downX = event.getX().toInt()

                            return true
                         else if (event.getAction() == MotionEvent.ACTION_UP) 
                            upX = event.getX().toInt()
                            if (upX - downX > 100) 
                                val animation = AnimationUtils.loadAnimation(applicationContext, R.anim.left_right_anim)
                                animation.setAnimationListener(object : Animation.AnimationListener 
                                    override fun onAnimationStart(animation: Animation) 

                                    override fun onAnimationRepeat(animation: Animation) 

                                    override fun onAnimationEnd(animation: Animation) 
                                        windowManager.removeViewImmediate(myview)
                                    
                                )
                                locklayout.startAnimation(animation)
                                // swipe right
                             else if (downX - upX > -100) 

                            
                            return true

                        
                        return false
                    
                )
             catch (ex: Exception)
            


            //Register receiver for determining screen off and if user is present
            mReceiver = LockScreenStateReceiver()
            val filter = IntentFilter(Intent.ACTION_SCREEN_OFF)
            filter.addAction(Intent.ACTION_USER_PRESENT)

            registerReceiver(mReceiver, filter)
        
        catch (ex:Exception)
        

        
    

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
         return START_STICKY
       

    inner class LockScreenStateReceiver : BroadcastReceiver() 
        override fun onReceive(context: Context, intent: Intent) 
            try 
                if (intent.action == Intent.ACTION_SCREEN_OFF) 
                    //if screen is turn off show the textview
                    if (!isShowing) 
                        windowManager.addView(myview, params)
                        isShowing = true
                    
                 else if (intent.action == Intent.ACTION_USER_PRESENT) 
                    //Handle resuming events if user is present/screen is unlocked remove the textview immediately
                    if (isShowing) 
                        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) 
                            //windowManager.removeViewImmediate(myview)
                        
                        isShowing = false
                    
                
            
            catch (ex:Exception)

            
        

    

    override fun onDestroy() 
        //unregister receiver when the service is destroy
        try 
            EventBus.getDefault().unregister(this)
            if (mReceiver != null) 
                unregisterReceiver(mReceiver)
            

            //remove view if it is showing and the service is destroy
            if (isShowing) 
                if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) 
                    //windowManager.removeViewImmediate(myview)
                
                isShowing = false
            
        
        catch (ex:Exception)
        

        
        super.onDestroy()
    


【问题讨论】:

【参考方案1】:

我非常感谢@Emir 的回答,但是他提供的代码没有在某些设备上运行,并且活动没有通过锁定屏幕启动

Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

这里的问题是代码的“或”部分导致某些设备出现问题,所以为了解决这个问题,我所做的不是在“或”部分中调用窗口标志,而是单独调用所有这些标志,这就是我所做的在我的 oncreate 中解决这个问题。

 override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_lock_screen)
   //execute all flags individually to solve this issue
     window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
        window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
        window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        val mUIFlag = ( View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                or View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_VISIBLE
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_IMMERSIVE
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)

        window.decorView.systemUiVisibility = mUIFlag

        

【讨论】:

我把这段代码放在 splash 它没有显示我的锁屏。我们应该在哪个活动中放置 windows 标志代码 @AndroidGeek 您需要将其放在活动的 oncreate 中,该活动旨在显示锁定屏幕,只需遵循活动生命周期,它应该可以工作,但是在某些设备中仍然存在一些问题但真的很少见! 实际上我只在服务中制作了我的叠加视图,但现在只是为了删除这个折旧的标志,我想制作一个透明的活动来显示我的视图..无论如何..谢谢!【参考方案2】:

我正在开发 VOIP,基本上我所做的就是当应用程序收到远程通知时,它会启动我的 IncomingCallActivity 以提醒用户有来电。它不需要覆盖或唤醒锁定权限。

您需要做的就是为您的锁定屏幕覆盖创建一个 Activity,从您的服务启动该 Activity。将相关的WindowManager 标志添加到您活动的Window onCreate 方法中。

 Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

我在奥利奥上测试过,效果很好。

【讨论】:

当我使用这些标志时无法按预期工作,它们仅在某些设备中显示,而且在某些具有 pie 的设备中,它无法正常工作,但是当我在三星标签中运行相同的代码时,它可以正常工作 你能告诉我你知道哪些设备不工作 是的,其中一些是三星 j2,itel I5503(Pie) 此代码主要不适用于大多数设备,但是当我在三星标签上运行它时,它可以正常工作,型号(SM-T285YD)。 你在三星 j2 和 itel I5593 上测试过 play.google.com/store/apps/… 这个应用吗

以上是关于在运行 Oreo 和 Pie 的设备中如何在锁定屏幕上覆盖布局的主要内容,如果未能解决你的问题,请参考以下文章

仅限 Android 9 (Pie):Context.startForegroundService() 没有调用 Service.startForeground() - 在 Oreo 上工作正常

奥利奥和派的后台服务问题

Android Oreo如何知道FCM消息已经到来?

Android Pie 9.0 将用户带到通知中心

我的 MainActivity 在使用 android 旧版本(例如 oreo)时滞后,但在最新版本中运行良好,如何解决?

如何在极简X环境下全屏运行火狐