我正在尝试实现连接服务类并希望向用户提供呼叫功能。我不想在通话中使用意图。Intent 将触发默认拨号应用程序,因此,我想制作自己的应用程序并将其设为默认值。
到目前为止,我已经实施了以下操作:
AndroidManifest.xml
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.dialapp">
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<!-- Needed only if your calling app reads numbers from the `PHONE_STATE` intent action. -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.MeriConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
</application>
</manifest>
相关代码MainActivity.java
:
public void register() {
TelecomManager manager = (TelecomManager) getSystemService(TELECOM_SERVICE);
PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(
new ComponentName(getPackageName(), MeriConnectionService.class.getName()),
MERI_CONNECTION_SERVICE_ID
);
PhoneAccount.Builder builder = PhoneAccount.builder(phoneAccountHandle, PHONE_ACCOUNT_LABEL);
builder.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_CONNECTION_MANAGER);
PhoneAccount phoneAccount = builder.build();
manager.registerPhoneAccount(phoneAccount);
}
public void call(View view) {
placeCall(mBinding.phoneNumberEt.getText().toString());
}
private void placeCall(String telNumber) {
TelecomManager telecomManager =
(TelecomManager) getSystemService(Context.TELECOM_SERVICE);
try {
Bundle extras = new Bundle();
Uri uri = Uri.fromParts("tel",
telNumber, null);
extras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, uri);
// Pass your own implementation of ConnectionService class to PhoneAccountHandle object here
PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(
new ComponentName(getPackageName(),
MeriConnectionService.class.getName()), MERI_CONNECTION_SERVICE_ID);
// Make sure to set the permission
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
// Pass the newly created PhoneAccountHandle to PhoneAccount object
PhoneAccount.Builder builder = new PhoneAccount.Builder(phoneAccountHandle, MERI_CONNECTION_SERVICE_ID);
// Make sure to set the capability
builder.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER);
PhoneAccount phoneAccount = builder.build();
// Register our own PhoneAccount to TelecomManager object
telecomManager.registerPhoneAccount(phoneAccount);
// This to show a prompt to the user to enable our own PhoneAccount because it is disabled by default
// There might be better way of this programmatically without the need of user interaction
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.server.telecom", "com.android.server.telecom.settings.EnableAccountPreferenceActivity"));
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
this.startActivity(intent);
// This is just for testing
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
List<PhoneAccountHandle> lstPA = telecomManager.getCallCapablePhoneAccounts();
int accoutSum = lstPA.size();
for (int i = 0; i < accoutSum; i++)
Log.d("MainActivity", "accountSum: " + accoutSum + lstPA.get(i));
for (PhoneAccountHandle account : lstPA) {
if (account.getComponentName().getClassName().equals(MeriConnectionService.class.getCanonicalName())) {
Log.d("Main Activity", account.getComponentName().getClassName());
break;
}
}
}
// Finally, make the call. This will bind our ConnectionService with TelecomManager.
// This will use the default dialer app user interface but using our own implementation of managing the call.
// By default, the system uses TelephonyConnectionService class
telecomManager.placeCall(uri, extras);
} catch (SecurityException se) {
Log.d("TESTAPP", se.getMessage());
}
}
}
现在,首先我使用以下代码尝试了电话的虚拟实现:
package com.example.dialapp.service;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.util.Log;
import android.widget.Toast;
import com.example.dialapp.R;
import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
public class MeriConnectionService extends ConnectionService {
private static final String LOG_TAG = MeriConnectionService.class.getSimpleName();
private static MeriConnectionService INSTANCE;
private final Handler mHandler = new Handler();
/**
* Used to play an audio tone during a call.
*/
private MediaPlayer mMediaPlayer;
public static MeriConnectionService getInstance() {
return INSTANCE;
}
@Override
public void onCreate() {
INSTANCE = this;
}
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerAccount,
final ConnectionRequest originalRequest) {
final Uri handle = originalRequest.getAddress();
String number = originalRequest.getAddress().getSchemeSpecificPart();
Log.d(LOG_TAG, "call, number: " + number);
// Crash on 555-DEAD to test call service crashing.
if ("5550340".equals(number)) {
throw new RuntimeException("Goodbye, cruel world.");
}
Bundle extras = originalRequest.getExtras();
String gatewayPackage = extras.getString(TelecomManager.GATEWAY_PROVIDER_PACKAGE);
Uri originalHandle = extras.getParcelable(TelecomManager.GATEWAY_ORIGINAL_ADDRESS);
if (extras.containsKey(TelecomManager.EXTRA_CALL_SUBJECT)) {
String callSubject = extras.getString(TelecomManager.EXTRA_CALL_SUBJECT);
Log.d(LOG_TAG, "Got subject: " + callSubject);
Toast.makeText(getApplicationContext(), "Got subject :" + callSubject,
Toast.LENGTH_SHORT).show();
}
Log.d(LOG_TAG, "gateway package [" + gatewayPackage + "], original handle [" +
originalHandle + "]");
final TestConnection connection =
new TestConnection(false /* isIncoming */, originalRequest);
// Have a special phone number to test the account-picker dialog flow.
if (number != null && number.contentEquals("09085686814")) {
connection.setDialing();
connection.startOutgoing();
for (Connection c : getAllConnections()) {
c.setOnHold();
}
} else {
Log.d(LOG_TAG, "Not a test number");
}
return connection;
}
private void activateCall(TestConnection connection) {
if (mMediaPlayer == null) {
mMediaPlayer = createMediaPlayer();
}
if (!mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
}
}
private MediaPlayer createMediaPlayer() {
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(USAGE_VOICE_COMMUNICATION)
.setContentType(CONTENT_TYPE_SPEECH)
.build();
final int audioSessionId = ((AudioManager) getSystemService(
Context.AUDIO_SERVICE)).generateAudioSessionId();
// Prepare the media player to play a tone when there is a call.
MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.ring, attributes,
audioSessionId);
mediaPlayer.setLooping(true);
return mediaPlayer;
}
private void destroyCall(TestConnection connection) {
// Ensure any playing media and camera resources are released.
//connection.stopAndCleanupMedia();
// Stops audio if there are no more calls.
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = createMediaPlayer();
}
}
final class TestConnection extends Connection {
/**
* Used to cleanup camera and media when done with connection.
*/
//private TestVideoProvider mTestVideoCallProvider;
private ConnectionRequest mOriginalRequest;
TestConnection(boolean isIncoming, ConnectionRequest request) {
mOriginalRequest = request;
int properties = getConnectionProperties();
setConnectionProperties(properties);
}
void startOutgoing() {
setDialing();
mHandler.postDelayed(() -> {
setActive();
activateCall(TestConnection.this);
}, 4000);
}
/**
* ${inheritDoc}
*/
@Override
public void onAbort() {
destroyCall(this);
destroy();
}
/**
* ${inheritDoc}
*/
@Override
public void onAnswer(int videoState) {
activateCall(this);
setActive();
}
/**
* ${inheritDoc}
*/
@Override
public void onPlayDtmfTone(char c) {
if (c == '1') {
setDialing();
}
}
/**
* ${inheritDoc}
*/
@Override
public void onStopDtmfTone() {
}
/**
* ${inheritDoc}
*/
@Override
public void onDisconnect() {
setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
destroyCall(this);
destroy();
}
/**
* ${inheritDoc}
*/
@Override
public void onHold() {
setOnHold();
}
/**
* ${inheritDoc}
*/
@Override
public void onReject() {
setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
destroyCall(this);
destroy();
}
/**
* ${inheritDoc}
*/
@Override
public void onUnhold() {
setActive();
}
}
}
对于实际实现,我指的是,com.android.services.telephony.TelephonyConnectionService
但我遇到的问题是我无法访问以下类的 android api:
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.cdma.CDMAPhone;
我的虚拟实现工作正常,所以我认为应该有一些方法来实现运营商呼叫,ConnectionService
但我无法找出如何?
请指导我如何实现这一目标。我将非常感谢你。