Search code examples
androidphone-callandroid-windowmanager

Dismissing a custom incoming call screen on Android when the call is answered or the phone stops ringing


I want to have a custom incoming call screen that shows up when there is an incoming call, and get dismissed if the call is answered or the phone stops ringing.

I've searched several posts on Stackoverflow to show me how can I implement that, and so far I am nearly there and after checking pros and cons of each approach in terms of how fast the screen gets shown, I've settled on the WindowManager approach.

Whenever I call wm.removeView(ly) I get the stack below

07-08 20:36:41.002  27547-27547/com.testtelephoney.customincomingcall E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.testtelephoney.customincomingcall, PID: 27547
java.lang.IllegalArgumentException: View=android.widget.LinearLayout{7e63aae V.E..... ......I. 0,0-0,0} not attached to window manager
        at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:402)
        at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:328)
        at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:84)
        at com.testtelephoney.customincomingcall.MyPhoneStateListener.onCallStateChanged(MyPhoneStateListener.java:43)
        at android.telephony.PhoneStateListener$2.handleMessage(PhoneStateListener.java:392)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:145)
        at android.app.ActivityThread.main(ActivityThread.java:5832)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)

Here is my code:

MyPhoneStateListener.java

import android.content.Context;
import android.graphics.PixelFormat;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;


public class MyPhoneStateListener extends PhoneStateListener{
    Context mContext;
    public MyPhoneStateListener(Context ct) {
        mContext = ct;
    }
    public void onCallStateChanged(int state,String incomingNumber){

        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT|WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);

        params.height= WindowManager.LayoutParams.MATCH_PARENT;
        params.width= WindowManager.LayoutParams.MATCH_PARENT;
        params.format=PixelFormat.TRANSPARENT;
        params.gravity= Gravity.TOP;

        LinearLayout ly;

        final LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ly=(LinearLayout)inflater.inflate(R.layout.activity_ic,null);


        switch (state){
            case TelephonyManager.CALL_STATE_IDLE:
                Log.d("DEBUG","IDLE");
                if(ly.getVisibility()== View.VISIBLE){
                    wm.removeView(ly);
                }
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                Log.d("DEBUG","OFFHOOK");
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                Log.d("DEBUG","RINGING");
                wm.addView(ly,params);
                break;
        }
    }
}

ServiceReceiver

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;

public class ServiceReceiver extends BroadcastReceiver {
    public ServiceReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        MyPhoneStateListener phoneListener = new MyPhoneStateListener(context);
        TelephonyManager telephony = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
        telephony.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
    }
}

AndroidManifest

    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.READ_PROFILE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <receiver android:name=".ServiceReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE" />
            </intent-filter>
     </receiver>

Solution

  • I've added a flag to mark that the view has been added. It does need to be improved off course.

     switch (state){
            case TelephonyManager.CALL_STATE_IDLE:
                Log.d("DEBUG","IDLE");
                if (viewCreated) {
                    wm.removeView(ly);
                    viewCreated = false;
                }
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                Log.d("DEBUG","OFFHOOK");
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                Log.d("DEBUG","RINGING");
                if (!viewCreated){
                    WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT|WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
                    params.height= WindowManager.LayoutParams.MATCH_PARENT;
                    params.width= WindowManager.LayoutParams.MATCH_PARENT;
                    params.format=PixelFormat.TRANSPARENT;
                    params.gravity= Gravity.TOP;
    
                    ly=(LinearLayout)inflater.inflate(R.layout.activity_login,null);
                    wm.addView(ly, params);
                    viewCreated=true;
                }
                break;
        }