Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4f4fda62 authored by Rahul Arya's avatar Rahul Arya Committed by Thomas Girardier
Browse files

[Pandora] Add InCallService to HFP Pandora server

Bind to the InCallService with appropriate permissions so we can do fun
stuff with calls.

Bug: 245578454
Test: atest pts-bot:HFP
Ignore-AOSP-First: Cherry-picked from AOSP
Merged-In: I7ff71b204d776595737b4a760bbe5de3d014c33f
Change-Id: I7ff71b204d776595737b4a760bbe5de3d014c33f
parent d393efef
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -27,6 +27,16 @@
                    <action android:name="android.media.browse.MediaBrowserService"/>
               </intent-filter>
          </service>

      <service
          android:name=".Hfp$PandoraInCallService"
          android:permission="android.permission.BIND_INCALL_SERVICE"
          android:exported="true">
        <intent-filter>
          <action android:name="android.telecom.InCallService" />
        </intent-filter>
      </service>

    </application>

  <uses-permission android:name="android.permission.INTERNET" />
+56 −5
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.pandora

import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothHeadset
import android.bluetooth.BluetoothManager
@@ -23,6 +24,10 @@ import android.bluetooth.BluetoothProfile
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.IBinder
import android.telecom.CallAudioState
import android.telecom.InCallService
import android.telecom.TelecomManager
import com.google.protobuf.Empty
import io.grpc.stub.StreamObserver
import kotlinx.coroutines.CoroutineScope
@@ -34,30 +39,48 @@ import kotlinx.coroutines.flow.shareIn
import pandora.HFPGrpc.HFPImplBase
import pandora.HfpProto.*

private const val TAG = "PandoraHfp"

@kotlinx.coroutines.ExperimentalCoroutinesApi
class Hfp(val context: Context) : HFPImplBase() {
  private val TAG = "PandoraHfp"

  private val scope: CoroutineScope
  private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
  private val flow: Flow<Intent>

  private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
  private val telecomManager = context.getSystemService(TelecomManager::class.java)!!
  private val bluetoothAdapter = bluetoothManager.adapter

  private val bluetoothHfp = getProfileProxy<BluetoothHeadset>(context, BluetoothProfile.HEADSET)

  companion object {
    @SuppressLint("StaticFieldLeak")
    private lateinit var inCallService: InCallService
  }

  init {
    scope = CoroutineScope(Dispatchers.Default)

    val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
    flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly)

    // kill any existing call
    telecomManager.endCall()
  }

  fun deinit() {
    // kill any existing call
    telecomManager.endCall()

    bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHfp)
    scope.cancel()
  }

  class PandoraInCallService : InCallService() {
    override fun onBind(intent: Intent?): IBinder? {
      inCallService = this
      return super.onBind(intent)
    }
  }

  override fun enableSlc(request: EnableSlcRequest, responseObserver: StreamObserver<Empty>) {
    grpcUnary<Empty>(scope, responseObserver) {
      val device = request.connection.toBluetoothDevice(bluetoothAdapter)
@@ -80,7 +103,7 @@ class Hfp(val context: Context) : HFPImplBase() {

  override fun setBatteryLevel(
    request: SetBatteryLevelRequest,
    responseObserver: StreamObserver<Empty>
    responseObserver: StreamObserver<Empty>,
  ) {
    grpcUnary<Empty>(scope, responseObserver) {
      val action = "android.intent.action.BATTERY_CHANGED"
@@ -88,4 +111,32 @@ class Hfp(val context: Context) : HFPImplBase() {
      Empty.getDefaultInstance()
    }
  }

  override fun declineCall(
    request: DeclineCallRequest,
    responseObserver: StreamObserver<DeclineCallResponse>,
  ) {
    grpcUnary(scope, responseObserver) {
      telecomManager.endCall()
      DeclineCallResponse.getDefaultInstance()
    }
  }

  override fun setAudioPath(
    request: SetAudioPathRequest,
    responseObserver: StreamObserver<SetAudioPathResponse>,
  ) {
    grpcUnary(scope, responseObserver) {
      when (request.audioPath!!) {
        AudioPath.AUDIO_PATH_UNKNOWN,
        AudioPath.UNRECOGNIZED -> {}
        AudioPath.AUDIO_PATH_HANDSFREE -> {
          check(bluetoothHfp.getActiveDevice() != null)
          inCallService.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH)
        }
        AudioPath.AUDIO_PATH_SPEAKERS -> inCallService.setAudioRoute(CallAudioState.ROUTE_SPEAKER)
      }
      SetAudioPathResponse.getDefaultInstance()
    }
  }
}
+20 −0
Original line number Diff line number Diff line
@@ -15,6 +15,10 @@ service HFP {
  rpc DisableSlc(DisableSlcRequest) returns (google.protobuf.Empty);
  // Change the battery level to the one requested
  rpc SetBatteryLevel(SetBatteryLevelRequest) returns (google.protobuf.Empty);
  // Decline a call
  rpc DeclineCall(DeclineCallRequest) returns (DeclineCallResponse);
  // Set the audio path
  rpc SetAudioPath(SetAudioPathRequest) returns (SetAudioPathResponse);
}

// Request of the `EnableSlc` method.
@@ -36,3 +40,19 @@ message SetBatteryLevelRequest {
  // Battery level to be set on the DUT
  int32 battery_percentage = 2;
}

message DeclineCallRequest {}

message DeclineCallResponse {}

enum AudioPath {
  AUDIO_PATH_UNKNOWN = 0;
  AUDIO_PATH_SPEAKERS = 1;
  AUDIO_PATH_HANDSFREE = 2;
}

message SetAudioPathRequest {
  AudioPath audio_path = 1;
}

message SetAudioPathResponse {}