1

I manage to get runtime permission for READ_PHONE_STATE but app doesn't react to phone state changes at all. I have also in manifest file READ_PHONE_STATE enabled.

Have tried with 3 Android phones but all fail to track state changes.

With iOS i manage to get READ_PHONE_STATE working with same code as i have tried with Android, but in iOS Apple has deprecated showing caller phone number.

    uses
      System.Permissions;

    {$R *.fmx}


    constructor TForm1.Create(AOwner: TComponent);
    const
      PermissionAccessReadPhoneState = 'android.permission.READ_PHONE_STATE';
      PermissionAccessMakeCall = 'android.permission.CALL_PHONE';
    begin
      inherited Create(AOwner);

      TPlatformServices.Current.SupportsPlatformService(IFMXPhoneDialerService, IInterface(PhoneDialerService));

      PermissionsService.RequestPermissions([PermissionAccessMakeCall, PermissionAccessReadPhoneState],
        procedure(const APermissions: TArray<string>; const AGrantResults: TArray<TpermissionStatus>)
        begin
          if (Length(AgrantResults) = 2) and (AgrantResults[0] = TPermissionStatus.Granted) and (AgrantResults[1] = TPermissionStatus.Granted) then
            begin
              ShowMessage('READ_PHONE_STATE + CALL_PHONE Activated!');
            end;
        end);

if Assigned(PhoneDialerService) then
    begin
      PhoneDialerService.OnCallStateChanged := MyOnCallStateChanged;
    end;
end;

    procedure TForm1.MyOnCallStateChanged(const ACallID: String;
      const ACallState: TCallState);
    var
      outText: String;
    begin
      case ACallState of
        TCallState.None:          outText := 'No calls';
        TCallState.Connected:     outText := 'Connected';
        TCallState.Incoming:      outText := 'Incoming Call';
        TCallState.Dialing:       outText := 'Dialing';
        TCallState.Disconnected:  outText := 'Disconnected';
      end;

      lblCallState.Text := outText;

    end;
Erva
  • 47
  • 8
  • 1
    Please provide a [mcve]. Without knowing exactly what you're doing, it's very difficult to know what the problem is – Dave Nottage Apr 14 '20 at 19:42
  • Added code to post – Erva Apr 14 '20 at 19:49
  • Why are you calling `PlatformServices.Current.SupportsPlatformService()` twice to assign the same `PhoneDialerService` variable? You should get rid of one of those assignments. I would suggest the 1st one, and move the 2nd one inside your anonymous procedure. You should not be assigning an `OnCallStateChanged` handler unless you are granted permissions first. – Remy Lebeau Apr 15 '20 at 00:11
  • Thanks for your reply. That second PhoneDialerService was mistake. Added PhoneDialer Service inside anonymous procedure before if statement. Moved OnCallStateChanged after permissions are granted. Also moved MyOnCallStatement procedure before TForm.Create. But still not able to get phone state changes. Can make a call and get carrier info. – Erva Apr 15 '20 at 05:44
  • If this doesn't work, another way to do it, is to implement a `PhoneStateListener` using Android API code. – Freddie Bell Apr 15 '20 at 06:43

2 Answers2

1
uses
  Androidapi.JNI.Telephony;

type
  TPhoneStateListener = class;
  TfrmTabbed = class(TForm)
    HeaderToolBar: TToolBar;
    ToolBarLabel: TLabel;
    TabControl1: TTabControl;
    ...

  private
    { Private declarations }
    PhoneStateListener: TPhoneStateListener;
    ...

  end;

  TPhoneStateListener = class(TJavaLocal, JICustomPhoneStateListener)
  private
    [weak]
    FParent : TfrmTabbed;
  public
    constructor Create(AParent : TfrmTabbed);
    procedure onCallForwardingIndicatorChanged(cfi: Boolean); cdecl;
    procedure onCallStateChanged(state: Integer; incomingNumber: JString); cdecl;
    procedure onCellInfoChanged(cellInfo: JList); cdecl;
    procedure onCellLocationChanged(location: JCellLocation); cdecl;
    procedure onDataActivity(direction: Integer); cdecl;
    procedure onDataConnectionStateChanged(state: Integer); overload; cdecl;
    procedure onDataConnectionStateChanged(state: Integer; networkType: Integer); overload; cdecl;
    procedure onMessageWaitingIndicatorChanged(mwi: Boolean); cdecl;
    procedure onServiceStateChanged(serviceState: JServiceState); cdecl;
    procedure onSignalStrengthChanged(asu: Integer); cdecl;
    procedure onSignalStrengthsChanged(signalStrength: JSignalStrength); cdecl;
  end;

procedure TfrmTabbed.FormShow(Sender: TObject);
var
  obj: JObject;
begin
  if PhoneStateListener = nil then
      PhoneStateListener := TPhoneStateListener.Create(Self);
    obj := TAndroidHelper.Context.getSystemService(TJContext.JavaClass.TELEPHONY_SERVICE);
    if obj = nil then
      raise Exception.Create('Telephony service not supported');
    CallinUIThread(procedure
    var
      TelephonyManager: JTelephonyManager;
      PSL: JCustomPhoneStateListener;
    begin
      TelephonyManager := TJTelephonyManager.Wrap(obj);
      PSL := TJCustomPhoneStateListener.JavaClass.init(PhoneStateListener);
      TelephonyManager.listen(PSL,
        TJPhoneStateListener.JavaClass.LISTEN_CALL_STATE
      );
    end
    );

{ TPhoneStateListener }

constructor TPhoneStateListener.Create(AParent: TfrmTabbed);
begin
  inherited Create;
  FParent := AParent;
end;

procedure TPhoneStateListener.onCallForwardingIndicatorChanged(cfi: Boolean);
begin
end;

procedure TPhoneStateListener.onCallStateChanged(state: Integer;
  incomingNumber: JString);
begin
  if state = TJTelephonyManager.JavaClass.CALL_STATE_IDLE then
  begin
  end
  else if state = TJTelephonyManager.JavaClass.CALL_STATE_OFFHOOK then
  begin
  end
  else if state = TJTelephonyManager.JavaClass.CALL_STATE_RINGING then
  begin
  end;
end;

procedure TPhoneStateListener.onCellInfoChanged(cellInfo: JList);
begin
end;

procedure TPhoneStateListener.onCellLocationChanged(location: JCellLocation);
begin
end;

procedure TPhoneStateListener.onDataActivity(direction: Integer);
begin
end;

procedure TPhoneStateListener.onDataConnectionStateChanged(state: Integer);
begin
end;

procedure TPhoneStateListener.onDataConnectionStateChanged(state: Integer; networkType: Integer);
begin
end;

procedure TPhoneStateListener.onMessageWaitingIndicatorChanged(mwi: Boolean);
begin
end;

procedure TPhoneStateListener.onServiceStateChanged(serviceState: JServiceState);
begin
end;

procedure TPhoneStateListener.onSignalStrengthChanged(asu: Integer);
begin
end;

procedure TPhoneStateListener.onSignalStrengthsChanged(signalStrength: JSignalStrength);
begin
end;
Freddie Bell
  • 2,186
  • 24
  • 43
  • I got it working almost. When app starts, CALL_STATE_IDLE executes. But when i make call or get call and end this calls, no CallStateChange is executed. Added runtime permissions to code. – Erva Apr 15 '20 at 15:37
  • Yes, I didn't include the Permissions code, because i knew you already knew how to do that! (And besides this code will work in 10.3.2 and earlier, without permissions). I know because it's from a working application. – Freddie Bell Apr 15 '20 at 16:11
  • One problem still exists, app doesn't execute CallStateChange when making call or receiving call. First thought it's because app goes background when Androids own phonecall window opens. Added button to app what makes call, but still CallStateChange don't execute when making call from app. – Erva Apr 15 '20 at 16:45
  • Android developers had the same problem. According to this [cannot-detect-when-outgoing-call-is-answered-in-android](https://stackoverflow.com/questions/13134331), you need to add `READ_PRECISE_PHONE_STATE` to the manifest. I had previously only used a phonestatelistener for signalstrength changes myself. – Freddie Bell Apr 15 '20 at 16:47
  • If you're unhappy that this ISN'T the complete solution, then revoke the answer tick. At the very least though, it's still a useful answer for other reasons. – Freddie Bell Apr 15 '20 at 16:52
  • 1
    You been great help, thanks again. I didn't expect complete solution, been very pleased that someone who knows about this, is willing to help me. I added to manifest READ_PRECISE_PHONE_STATE and now managed to get CALL_STATE_OFFHOOK to execute. It executed once when i made call right from the app. Need to play with this little more and get to know when these different states trigger. – Erva Apr 15 '20 at 17:09
  • I found that my phonestatelistner worked "better" when it was in an Android Service. – Freddie Bell Apr 15 '20 at 18:12
  • Been playing with this and sometimes it works, sometimes not. Come to my mind also that it might be better as an Android Service. – Erva Apr 15 '20 at 18:25
  • If you do this in a Service, you don't need to `CallinUiThread`. Don't forget to do `TJPhoneStateListener.JavaClass.LISTEN_NONE` when the app shuts down. – Freddie Bell Apr 16 '20 at 05:57
  • @Erva Do you have a stable version? I have to do something similar in Delphi. Will you share your knowledge? ;) – Olaf Oct 09 '20 at 11:52
  • No, didn't manage to get phone trigger with every call. Leaved this way to solve problem to get client calls duration to db. We now move call info from phone call log to db. – Erva Oct 11 '20 at 13:51
1

I had the same problem. I've just got the first status after loading the form of the app but no more status changings are triggered. I solved the problem by running the use of the PhoneStateListener/TelephonyManager in a separate thread. Then the status change of the phone is properly triggered.

carbacan
  • 11
  • 1