Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android displaying floating widget overlay not working

I want my my app to display a floating bubble notification as in facebook messenger. Following https://www.androidhive.info/2016/11/android-floating-widget-like-facebook-chat-head/ below is the service I wrote.

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

public class BubbleNotifyService extends Service {

    private WindowManager windowManager;
    private View BubbleView;
    private TextView bubbleTitle, bubbleData;

    public BubbleNotifyService() {
    }

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

    @SuppressLint({"RtlHardcoded", "InflateParams"})
    @Override
    public void onCreate() {
        super.onCreate();
        //android.os.Debug.waitForDebugger();

        setTheme(R.style.AppTheme);

        BubbleView =
                LayoutInflater.from(this).inflate(R.layout.floating_bubble, null);

        final WindowManager.LayoutParams params;

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT
        );
    } else {
        params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ,
                PixelFormat.TRANSLUCENT
        );
    }

        if (BubbleView == null) {
            Log.d("Bubble", "BubbleView is null.");
        }

        params.gravity = Gravity.TOP | Gravity.LEFT;
        params.x = 0;
        params.y = 100;

        windowManager =
                (WindowManager) getSystemService(WINDOW_SERVICE);

        if (windowManager != null) {
            Log.d("Bubble", "added bubble to view.");
            windowManager.addView(BubbleView, params);
        } else {
            Log.d("Bubble", "windowManager is null");
        }

        final View collapsedView =
                BubbleView.findViewById(R.id.collapsed_view);
        final View expandedView =
                BubbleView.findViewById(R.id.expanded_view);

        expandedView.setVisibility(View.GONE);
        collapsedView.setVisibility(View.VISIBLE);


        ImageView close_collapsed = BubbleView.findViewById(R.id.collaspsed_cancel);
        ImageView close_expanded = BubbleView.findViewById(R.id.expanded_cancel);
        Button open_act_btn = BubbleView.findViewById(R.id.open_full_btn);
        bubbleTitle = BubbleView.findViewById(R.id.bubble_title);
        bubbleMeaning = BubbleView.findViewById(R.id.bubble_data);

        close_collapsed.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stopSelf();
            }
        });

        close_expanded.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                collapsedView.setVisibility(View.VISIBLE);
                expandedView.setVisibility(View.GONE);
            }
        });

        open_act_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //TODO new Activity
            }
        });

        BubbleView.findViewById(R.id.bubble_root).setOnTouchListener(new View.OnTouchListener() {

            private int initialX;
            private int initialY;
            private float initialTouchX;
            private float initialTouchY;

            @SuppressLint("ClickableViewAccessibility")
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {

                switch (motionEvent.getAction()) {

                    case MotionEvent.ACTION_DOWN:
                        initialX = params.x;
                        initialY = params.y;

                        initialTouchX = motionEvent.getRawX();
                        initialTouchY = motionEvent.getRawY();
                        return true;

                    case MotionEvent.ACTION_UP:
                        int Xdiff = (int) (motionEvent.getRawX() - initialTouchX);
                        int Ydiff = (int) (motionEvent.getRawY() - initialTouchY);

                        if (Xdiff < 10 && Ydiff < 10) {
                            if (isViewCollapsed()) {
                                collapsedView.setVisibility(View.GONE);
                                expandedView.setVisibility(View.VISIBLE);
                            }
                        }
                        return true;

                    case MotionEvent.ACTION_MOVE:
                        params.x =
                                initialX + (int) (motionEvent.getRawX() - initialTouchX);
                        params.y =
                                initialY + (int) (motionEvent.getRawY() - initialTouchY);

                        windowManager.updateViewLayout(BubbleView, params);
                        return true;
                }
                return false;
            }
        });

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        String title = intent.getStringExtra(IntentKeys.BUBBLE_DATA_TITLE);
        String data = intent.getStringExtra(IntentKeys.BUBBLE_DATA);

        bubbleTitle.setText(title);
        bubbleData.setText(data);

        return START_NOT_STICKY;
    }

    private boolean isViewCollapsed() {
        return BubbleView == null ||
                BubbleView.findViewById(R.id.collapsed_view).getVisibility() == View.VISIBLE;
    }


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

        if (BubbleView != null) {
            windowManager.removeView(BubbleView);
        }
    }
}

BubbleNotifyService is called from another service that receives notification as:

Intent intent = new Intent(context, BubbleNotifyService.class);
                intent.putExtra(IntentKeys.BUBBLE_DATA_TITLE, Results.getWord());
                intent.putExtra(IntentKeys.BUBBLE_DATA, Results.getData());
                context.startService(intent);

All data from the intent is passed to BubbleNotifyService. The service is running as a separate process as specified in manifest android:process=":BubbleResult", But the service does not display any overlay. Draw over other app permission is granted to the app.

Layout for bubble(floating_bubble.xml)

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/bubble_frame"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <android.support.constraint.ConstraintLayout
        android:id="@+id/bubble_root"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">


        <android.support.constraint.ConstraintLayout
            android:id="@+id/collapsed_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="visible">

            <ImageView
                android:id="@+id/collapsed_icon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:srcCompat="@mipmap/ic_launcher"
                tools:ignore="ContentDescription" />

            <ImageView
                android:id="@+id/collaspsed_cancel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintStart_toEndOf="@+id/collapsed_icon"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_action_cancel"
                tools:ignore="ContentDescription" />

        </android.support.constraint.ConstraintLayout>

        <android.support.v7.widget.CardView
            android:id="@+id/expanded_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="visible"
            app:cardCornerRadius="5dp"
            app:cardElevation="10dp"
            app:cardUseCompatPadding="true"
            app:contentPadding="5dp">

            <android.support.constraint.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <TextView
                    android:id="@+id/bubble_title"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:textSize="24sp"
                    android:textStyle="bold"
                    app:layout_constraintEnd_toStartOf="@id/expanded_cancel"
                    app:layout_constraintHorizontal_bias="0.0"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    tools:text="@string/result_card_title" />

                <TextView
                    android:id="@+id/bubble_data"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:textSize="18sp"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@id/bubble_title"
                    tools:text="@string/result_card_data" />

                <ImageView
                    android:id="@+id/expanded_cancel"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toEndOf="@id/bubble_title"
                    app:srcCompat="@drawable/ic_action_cancel"
                    tools:ignore="ContentDescription" />

                <Button
                    android:id="@+id/open_full_btn"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="8dp"
                    android:layout_marginTop="8dp"
                    android:text="@string/result_card_see_more"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/bubble_data" />


            </android.support.constraint.ConstraintLayout>
        </android.support.v7.widget.CardView>

    </android.support.constraint.ConstraintLayout>


</FrameLayout>
like image 389
Pushkar Avatar asked Jul 11 '18 12:07

Pushkar


1 Answers

Tested both Your code and mentioned blog post's code. The problem seems to be because You have used ConstraintLayout and CardView in Your layout.

For widget behavior, these view types are not supported. When we add view directly to the system window they behave likes RemoteViews. As soon as You change ConstraintLayout and CardView, Your layout becomes visible and operable.

like image 138
theJango Avatar answered Sep 30 '22 11:09

theJango