Background introduction

Recently, I have an idea prototype of an App project, and I am doing various trials and researches. In the process, I found some unfamiliar knowledge points and minor changes caused by Android version update. Therefore, I want to record the scattered knowledge points during the development process, so that when the App is released, I can see how many small and trivial knowledge points are accumulated into usable business functions. Although each knowledge point is not difficult, but this process shows the final step from learning to practical application, may help beginners.

Floating window

Suspension window is a way to display information to users independent of the UI system constructed by activities and fragments. Its unique function lies in that it can provide consistent data display across pages and even across applications, or provide some common quick operations. The memory usage display and quick cleanup in the early days of mobile housekeeping software is a typical application, but it has been gradually phased out. Most apps now don’t use hover Windows, except for videos that play in small Windows.

Hover Windows are still a great debugging tool for developers, and by adding hover Windows to test packages you can achieve ADB independent debugging, which is especially useful in certain scenarios.

The hover window has an exclusive permission control, which can only be displayed after the user opens the permission.

Register in Androidmanifest.xml:

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

Check whether permissions are granted (API Level 23+) :

    private fun checkOverlayPermission(context: Context): Boolean{
    	return Build.VERSION.SDK_INT >= 23 && Settings.canDrawOverlays(context)
    }
Copy the code

Applying for permissions and handling callbacks:

    private fun requestOverlayPermission(a) {
        val intent = Intent(
            Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
            Uri.parse("package:$packageName"))// Select FLAG_NEW_TASK as required
        startActivityForResult(intent, 101)}override fun onActivityResult(requestCode: Int, resultCode: Int.data: Intent?). {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 101 && resultCode == RESULT_OK) {
            // checkOverlayPermission() // Can be rechecked to avoid resultCode return value error}}Copy the code

After obtaining the permissions, you can display the hover window through WindowManager. Suspension window is essentially a View. In order to control and manage the suspension window at any time, we can put the whole suspension window into a Service to avoid relying on specific activities. It also allows the hover window View to have a proper life cycle (Service#onCreate to Service#onDestroy).

Display: parameter and coordinate control

The WindowManager API is not too complicated, and with the API and knowledge of the View’s coordinate system, you can make the hover window display in the right place. WindowManager controllable parameters are WindowManager. LayoutParams.

// A reference
	windowLayoutParams = WindowManager.LayoutParams().apply {
            type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                WindowManager.LayoutParams.TYPE_PHONE
            }
            flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                    WindowManager.LayoutParams.FLAG_FULLSCREEN
            format = PixelFormat.RGBA_8888
            x = 0
            y = 0
            gravity = Gravity.START or Gravity.TOP
            width = WindowManager.LayoutParams.WRAP_CONTENT
            height = WindowManager.LayoutParams.WRAP_CONTENT
        }
Copy the code

Type is a key parameter. WIndowManager is not designed specifically for hover Windows. Setting type to TYPE_APPLICATION_OVERLAY is a reasonable hover window configuration.

Flags specifies the View behavior. Some parameters take effect only when flags are set. For example, dimAmount takes effect only when FLAG_DIM_BEHIND is set.

FLAG variable name meaning
FLAG_DIM_BEHIND Add a dialog-like background darkening effect and modify dimAmount to adjust the degree of darkening
FLAG_NOT_FOCUSABLE Unable to get focus, not affected in touch mode
FLAG_NOT_TOUCHABLE Can’t get touch event, touch screen can’t interact
FLAG_LAYOUT_NO_LIMITS The View can extend beyond the visible area of the screen

Note that if you look at the code prompt you’ll notice that there are other constants named FLAG_. Not all flags apply to hover Windows. FLAG_FULLSCREEN, for example, is invalid in hover window scenarios. Can the WindowManager. LayoutParams. Flags in the field of annotation to see all of the FLAG types and meanings, most of them are on the function of the Activity.

Format must be set to pixelformat.rgba_8888 to display transparent channels. Suspension Windows usually need to be translucent to avoid blocking applications below. Gravity determines the alignment of the View, i.e. the origin of the current coordinate system. When set to gravity.START or gravity.TOP, the origin is in the upper left corner, which is basically the same as the screen coordinate system. Width and height are the dimensions of the View.

The origin of the coordinate system based on Gravity stays the same while the length of x and y axis is changed. If the floating window coordinates are not changed, it may be out of the screen in the new coordinate system. This should be handled in onConfigurationChanged. As you rotate the screen, you can keep the relative positions roughly the same by calculating the percentage of coordinates on the XY axis.

[Before processing ↓]

[Processed ↓]

(It doesn’t seem to record correctly when the screen is rotated, just focus on the results before and after)

Operation: Click, drag, expand, adsorb

Mobile operators is the basis of the suspension window View position, adjust the position of way is through WindowManager# updateViewLayout modify WindowManager. LayoutParams.

    private fun moveViewTo(x: Float, y: Float){
// Log.e("asdfg", "move float view to $x, $y")
        windowLayoutParams.apply {
            this.x = x.toInt()
            this.y = y.toInt()
        }
        mWindowManager.updateViewLayout(mFloatView, windowLayoutParams)
    }
Copy the code

To do this, you must use onTouchListener. Clicking and automatically attaching edges can also be done together.

When dragging, click position is a point in the View, but set the position of the View is the point in the upper left corner, need to calculate the gap between the two points, always maintain the relative distance.

    private var mTouchX = 0f
    private var mTouchY = 0f
    / /...
    floatView.setOnTouchListener { v, event ->
            when(event.actionMasked){
                MotionEvent.ACTION_DOWN -> {
                    mTouchX = event.x
                    mTouchY = event.y
                    return@setOnTouchListener true
                }
                MotionEvent.ACTION_MOVE -> {
                    moveViewTo(event.rawX - mTouchX, event.rawY - mTouchY - GlobalStatus.statusBarHeight) 
                    RawY contains the height of the status bar. If you do not remove it, the first move will move the height of the status bar by one more distance
                }
                MotionEvent.ACTION_UP -> {
                    / /...
                }
                return@setOnTouchListener false
            }

Copy the code

Handle click and autoattach effects in ACTION_UP. The judgment of click should be separated from that of drag as far as possible. It should be judged as click only when the drag is an invisible distance or does not move at all. Generally, it is not expected to trigger the click event when dragging.

    private var mMoveStartX = 0f
    private var mMoveStartY = 0f
    / /...
    
    when(event.actionMasked){
        MotionEvent.ACTION_DOWN -> {
            mMoveStartX = event.rawX
            mMoveStartY = event.rawY
            return@setOnTouchListener true
        }
        MotionEvent.ACTION_UP -> {
            if (abs(event.rawY - mMoveStartX + event.rawX - mMoveStartY) < 2){
                v.performClick() // Click events are still set via setOnClickListener}}}Copy the code

Automatic adsorption is also a View movement, according to the needs can be considered directly in one step and do displacement animation two ways. Animation can be implemented with ValueAnimator and WindowManager. It is necessary to pay attention to the animation process when the View is blocked by touch, to cancel the animation in the first time, to avoid the View left and right horizontal jump.

// Stick to the left and right edges.

/ /...
MotionEvent.ACTION_UP -> {
    val edgeX = if (GlobalStatus.screenWidth / 2 > event.rawX) {
        0f
    } else{ GlobalStatus.screenWidth - mFloatView!! .width.toFloat() } moveViewTo(edgeX, event.rawY - mTouchY - GlobalStatus.statusBarHeight) }/ /...
Copy the code

[Effect display ↓]

Data update

The hover window may require data from outside the Service to be updated. The data displayed in the hover window is actually the data passed between other components and the Service. Just pass the data in the Intent.

Knowledge extension

Why is there no touch event for the hover window View and the blocked App View will not respond to the touch event?

In the event distribution mechanism, the event starts at the Window. The hover Window View added with WindowManager is not in the same Window as the Activity displayed below. When FLAG_NOT_TOUCHABLE is not set, the touch event is assigned directly to the hover Window. Even if the touch event is not consumed, it is no longer passed to the following Activity’s Window.

The next step

Access the AccessibilityService and display the name of the current Activity in the suspension window (for debugging, not App functionality). The instructions for using AccessibilityService have been written before, and will be reedited if new problems are found. Portal: Android notes – AccessibilityService

Access MediaProjection and read the information displayed on the screen according to certain rules (in the form of images).

If you find any errors or questions in this article, please feel free to comment and let me know. Thanks to every reader. So ~ ~ da ~ yukio okamoto, ~ ko ko