9

How do I change the phone call user interface? Like I have my own dialer layout and contacts layout but how do I change the calling UI. So, when the call is going on, can I remove the speaker button for example?

Here is my dialer scene that I have created: Dialer Picture

But I don't know how to edit this screen: Calling Picture

EDIT: I have already built the UI, I just can not get it to show during call!

Here is the code for as a simpler version:

public class MainActivity extends Activity {

private Button callBtn;
private Button dialBtn;
private EditText number;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    number = (EditText) findViewById(R.id.phoneNumber);
    callBtn = (Button) findViewById(R.id.call);
    dialBtn = (Button) findViewById(R.id.dial);

    // add PhoneStateListener for monitoring
    MyPhoneListener phoneListener = new MyPhoneListener();
    TelephonyManager telephonyManager = 
        (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
    // receive notifications of telephony state changes 
    telephonyManager.listen(phoneListener,PhoneStateListener.LISTEN_CALL_STATE);

    callBtn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            try {
                // set the data
                String uri = "tel:"+number.getText().toString();
                Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse(uri));

                startActivity(callIntent);
            }catch(Exception e) {
                Toast.makeText(getApplicationContext(),"Your call has failed...",
                    Toast.LENGTH_LONG).show();
                e.printStackTrace();
            }
        }
    });

    dialBtn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            try {
                String uri = "tel:"+number.getText().toString();
                Intent dialIntent = new Intent(Intent.ACTION_DIAL, Uri.parse(uri));

                startActivity(dialIntent);
            }catch(Exception e) {
                Toast.makeText(getApplicationContext(),"Your call has failed...",
                    Toast.LENGTH_LONG).show();
                e.printStackTrace();
            }
        }
    });
}

private class MyPhoneListener extends PhoneStateListener {

    private boolean onCall = false;

    @Override
    public void onCallStateChanged(int state, String incomingNumber) {

        switch (state) {
        case TelephonyManager.CALL_STATE_RINGING:
            // phone ringing...
            Toast.makeText(MainActivity.this, incomingNumber + " calls you", 
                    Toast.LENGTH_LONG).show();
            break;

        case TelephonyManager.CALL_STATE_OFFHOOK:
            // one call exists that is dialing, active, or on hold
            Toast.makeText(MainActivity.this, "on call...", 
                    Toast.LENGTH_LONG).show();
            //because user answers the incoming call
            onCall = true;
            break;

        case TelephonyManager.CALL_STATE_IDLE:
            // in initialization of the class and at the end of phone call 

            // detect flag from CALL_STATE_OFFHOOK
            if (onCall == true) {
                Toast.makeText(MainActivity.this, "restart app after call", 
                        Toast.LENGTH_LONG).show();

                // restart our application
                Intent restart = getBaseContext().getPackageManager().
                    getLaunchIntentForPackage(getBaseContext().getPackageName());
                restart.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(restart);

                onCall = false;
            }
            break;
        default:
            break;
        }

    }
}
}

Thanks!

Sevle
  • 3,109
  • 2
  • 19
  • 31
Ash Patel
  • 111
  • 1
  • 1
  • 6

4 Answers4

6

Add calling permission in manifest

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

Then need to check if call button pressed. for that use below intent filter

<intent-filter>
    <action android:name="android.intent.action.CALL_BUTTON" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

and when firing the UI

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <action android:name="android.intent.action.DIAL" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="tel" />
</intent-filter>

that means your calling activity in manifest will be something like this

<activity
        android:name="com.example.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <!-- open activity when establishing a call -->
        <intent-filter>
            <action android:name="android.intent.action.CALL_PRIVILEGED" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="tel" />
        </intent-filter>

    </activity>
Ameer
  • 2,709
  • 1
  • 28
  • 44
0

EDIT: Actually the api 23 was release, check this answer. Keep in mind that some devices may not support this functionality.

PREVIEW:

The actual calling view (what you see during calls) CAN`T be changed.

To be more specific, some parts of android CAN NOT be overridden, android has his core and as developers we have limited access to this core. In your case you can override dialer but you can`t override actual phone call view.

You can't do anything about this, until android team decided to share this core feature with us (developers).

Vasil Valchev
  • 5,701
  • 2
  • 34
  • 39
  • Please be more descriptive, and maybe tell what he could do since what he wants doesn't work, thanks. – Aaron Esau Apr 14 '16 at 14:56
  • Ash Patel ask "I just can not get it to show during call!", and the answer is: No, you can`t, because nobody can! – Vasil Valchev Apr 15 '16 at 08:00
  • yes, read mine answer, before downvoting, its end with "You can't do anything about this, until android team decided to share this core feature with us (developers)." so basically the api was predictable changed, – Vasil Valchev Dec 30 '20 at 13:49
0

since API 23 it is possible, see Replacing default Phone app on Android 6 and 7 with InCallService arekolek.

Kotlin version: Simple Phone

Java version: Simple Phone Dialer

EDIT

As recommended, Below is the simplest java version.

Below is shown the Manifest with the intent-filter needed.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.aliton.customphonecall">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".DialerActivity" >
            <intent-filter>

                <!-- Handle links from other applications -->
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.DIAL" />
                <!-- Populate the system chooser -->
                <category android:name="android.intent.category.DEFAULT" />
                <!-- Handle links in browsers -->
                <category android:name="android.intent.category.BROWSABLE" />

                <data android:scheme="tel" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.DIAL" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <service
            android:name=".CallService"
            android:permission="android.permission.BIND_INCALL_SERVICE">
            <meta-data
                android:name="android.telecom.IN_CALL_SERVICE_UI"
                android:value="true" />

            <intent-filter>
                <action android:name="android.telecom.InCallService" />
            </intent-filter>
        </service>

        <activity android:name=".CallActivity"></activity>
    </application>

</manifest>

The MainActivity just have a Button with an Intent redirecting to the DialerActivity.

Below is the DialerActivity. In this Activity, you will set your app as default in order to make call with your UI.

public class DialerActivity extends AppCompatActivity {

    private static final int REQUEST_CALL_PHONE = 10;

    EditText phoneNumber;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dialer);

        phoneNumber = (EditText) findViewById(R.id.etNumber);
        Button bCall = (Button) findViewById(R.id.btnCall);

        offerReplacingDefaultDialer();

        bCall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                makeCall();
            }
        });
    }

    private void makeCall() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
            Uri uri = Uri.parse("tel:"+phoneNumber.getText().toString().trim());
            Intent Call = new Intent(Intent.ACTION_CALL, uri);
            //Toast.makeText(this, "Entered makeCall()", Toast.LENGTH_SHORT).show();
            Log.i("debinf Dialer", "Entered makeCall()");
            startActivity(Call);

        } else {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CALL_PHONE);
        }
    }

    private void offerReplacingDefaultDialer() {
        if (getSystemService(TelecomManager.class).getDefaultDialerPackage() != getPackageName()) {
            Intent ChangeDialer = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
            ChangeDialer.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName());
            startActivity(ChangeDialer);
        }

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQUEST_CALL_PHONE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                makeCall();
            } else {
                Toast.makeText(this, "calling permission denied", Toast.LENGTH_LONG).show();
            }
            //return;
        }
    }

}

Here is how InCallService is implemented to manage the calls.

public class CallService extends InCallService {

    OngoingCallObject ongoingCallObject;

    @Override
    public void onCallAdded(Call call) {
        super.onCallAdded(call);
        new OngoingCallObject().setCall(call);

        //Intent CallAct = new Intent(this, CallActivity.class);
        //startActivity(CallAct);

        CallActivity.start(this, call);
    }

    @Override
    public void onCallRemoved(Call call) {
        super.onCallRemoved(call);
        new OngoingCallObject().setCall(null);
    }

}

Here is how the Object is implemented.

public class OngoingCallObject {

    private static Call call;

    private Object callback = new Callback() {
        @Override
        public void onStateChanged(Call call, int state) {
            super.onStateChanged(call, state);
            Log.i("debinf OngoingObj", "state is "+state);
        }
    };

    public void setCall(Call call) {
        if (this.call != null) {
            this.call.unregisterCallback((Call.Callback)callback);
        }

        if (call != null) {
            call.registerCallback((Call.Callback)callback);
            Log.i("debinf OngoingObj", "call.getState() is "+call.getState());
        }

        this.call = call;
    }

    public void answer() {
        //assert this.call != null;
        if (this.call != null) {
            this.call.answer(VideoProfile.STATE_AUDIO_ONLY);
        }
    }

    public void hangup() {
        //assert this.call != null;
        if (this.call != null) {
            this.call.disconnect();
        }
    }
}

And finally, the CallActivity where you launch your UI.

public class CallActivity extends AppCompatActivity {

    TextView callInfo;
    Button answer, hangup;

    private String number;
    private OngoingCallObject ongoingCall;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);

        ongoingCall = new OngoingCallObject();

        answer = (Button) findViewById(R.id.answer);
        hangup = (Button) findViewById(R.id.hangup);
        callInfo = (TextView) findViewById(R.id.callInfo);

        number = Objects.requireNonNull(getIntent().getData().getSchemeSpecificPart());

        callInfo.setText("Calling number : "+number);

        answer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Toast.makeText(CallActivity.this, "Answer button", Toast.LENGTH_SHORT).show();
                ongoingCall.answer();
            }
        });

        hangup.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ongoingCall.hangup();
            }
        });


    }

    public static void start(Context context, Call call) {
        Intent intent = new Intent(context, CallActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setData(call.getDetails().getHandle());
        context.startActivity(intent);
    }

}

Here is the xml for DialerActivity: activity_dialer.xml

<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DialerActivity">

    <EditText
        android:id="@+id/etNumber"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Tel number"/>

    <Button
        android:id="@+id/btnCall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="CallActivity"
        android:layout_below="@+id/etNumber" />

</RelativeLayout>

Here is the xml for CallActivity: activity_call.xml

<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CallActivity">

    <TextView
        android:id="@+id/callInfo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.3"
        tools:text="Hello World!" />

    <Button
        android:id="@+id/answer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Answer"
        app:layout_constraintBaseline_toBaselineOf="@+id/hangup"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/hangup" />

    <Button
        android:id="@+id/hangup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hang up"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/answer"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/callInfo" />


</android.support.constraint.ConstraintLayout>

I hope it helps!

Aliton Oliveira
  • 1,224
  • 1
  • 15
  • 26
  • It is not working on Xiaomi devices. i.e. CallService not invoking during call. – Giru Bhai Aug 10 '19 at 05:11
  • 2
    I am using appropriate level, that's why its working except xiaomi devices. – Giru Bhai Aug 13 '19 at 06:29
  • 1
    +1 for some device are not supporting this, make sure to check the brand like this: https://stackoverflow.com/a/27836910/1683466 and exclude Xiomi, and also this need to be tested on huawei too. – Vasil Valchev Dec 30 '20 at 13:59
-2

Build your own Dialer UI. Check this out. You will need an Activity to handle the intent and then displaying a custom UI is your business.

Community
  • 1
  • 1
TheSunny
  • 371
  • 4
  • 14