Search code examples
androiddelphiphone-calldelphi-10-seattle

How to check and hang-up/reject incoming/outgoing calls on Android in Delphi?


Is there any solution to programmatically check and hang-up/reject incoming/outgoing calls on Android in Delphi?


Solution

  • First, you must have own BroadcastReceiver unit - you can download it from here.

    unit CSBroadcastReceiver;
    
    interface
    
    uses
      System.Classes
      ,System.SysUtils
      {$IFDEF ANDROID}
      ,Androidapi.JNI.Embarcadero
      ,Androidapi.JNI.GraphicsContentViewText
      ,Androidapi.Helpers
      ,Androidapi.JNIBridge
      ,Androidapi.JNI.JavaTypes
      ,Androidapi.JNI.App
      {$ENDIF}
      ;
    
    type
    
      {$IFNDEF ANDROID}
      JIntent = class
      end;
      JContext = class
      end;
      {$ENDIF}
    
      TCSBroadcastReceiver= class;
      TOnReceive = procedure (csContext: JContext; csIntent: JIntent) of object;
    
      {$IFDEF ANDROID}
      TCSListener = class(TJavaLocal, JFMXBroadcastReceiverListener)
        private
          FOwner: TCSBroadcastReceiver;
        public
          constructor Create(AOwner: TCSBroadcastReceiver);
          procedure OnReceive(csContext: JContext; csIntent: JIntent); cdecl;
      end;
      {$ENDIF}
    
    
      TCSBroadcastReceiver = class(TComponent)
        private
          {$IFDEF ANDROID}
          FReceiver: JBroadcastReceiver;
          FListener : TCSListener;
          {$ENDIF}
          FOnReceive: TOnReceive;
          FItems: TStringList;
          function GetItem(const csIndex: Integer): String;
    
        public
          constructor Create(AOwner: TComponent); override;
          destructor  Destroy; override;
          procedure SendBroadcast(csValue: String);
          procedure Add(csValue: String);
          procedure Delete(csIndex: Integer);
          procedure Clear;
          procedure setResultData(data: JString);
          function Remove(const csValue: String): Integer;
          function First: String;
          function Last: String;
          function HasPermission(const csPermission: string): Boolean;
          procedure RegisterReceive;
          property Item[const csIndex: Integer]: string read GetItem; default;
          property Items: TStringList read FItems write FItems;
        published
          property OnReceive: TOnReceive read FOnReceive write FOnReceive;
      end;
    
    procedure Register;
    
    implementation
    
    procedure Register;
    begin
      RegisterComponents('Classicsoft', [TCSBroadcastReceiver]);
    end;
    
    { TCSBroadcastReceiver }
    
    procedure TCSBroadcastReceiver.setResultData(data: Jstring);
    begin
      FReceiver.setResultData(data);
    end;
    
    procedure TCSBroadcastReceiver.Add(csValue: String);
    {$IFDEF ANDROID}
    var
      Filter: JIntentFilter;
    {$ENDIF}
    begin
      {$IFDEF ANDROID}
      if (FListener = nil) or (FReceiver = nil) then
      begin
        Raise Exception.Create('First use RegisterReceive!');
        Exit;
      end;
      {$ENDIF}
    
      if FItems <> nil then
        if FItems.IndexOf(csValue) = -1 then
        begin
        {$IFDEF ANDROID}
          filter := TJIntentFilter.Create;
          filter.addAction(StringToJString(csValue));
          TAndroidHelper.Context.registerReceiver(FReceiver, filter);
        {$ENDIF}
          FItems.Add(csValue);
        end;
    end;
    
    procedure TCSBroadcastReceiver.Clear;
    begin
      FItems.Clear;
    end;
    
    constructor TCSBroadcastReceiver.Create(AOwner: TComponent);
    begin
      inherited;
      FItems := TStringList.Create;
    end;
    
    procedure TCSBroadcastReceiver.Delete(csIndex: Integer);
    begin
      if FItems <> nil then
      begin
        FItems.Delete(csIndex);
        {$IFDEF ANDROID}
          TAndroidHelper.Activity.UnregisterReceiver(FReceiver);
          RegisterReceive;
        {$ENDIF}
      end;
    end;
    
    destructor TCSBroadcastReceiver.Destroy;
    begin
      FItems.Free;
      {$IFDEF ANDROID}
      if FReceiver <> nil  then
        TAndroidHelper.Activity.UnregisterReceiver(FReceiver);
      {$ENDIF}
      inherited;
    end;
    
    function TCSBroadcastReceiver.First: String;
    begin
      Result := FItems[0];
    end;
    
    function TCSBroadcastReceiver.GetItem(const csIndex: Integer): String;
    begin
      Result := FItems[csIndex];
    end;
    
    function TCSBroadcastReceiver.HasPermission(const csPermission: string): Boolean;
    {$IFDEF ANDROID}
    begin
      Result := TAndroidHelper.Activity.checkCallingOrSelfPermission(StringToJString(csPermission)) = TJPackageManager.JavaClass.PERMISSION_GRANTED;
    {$ELSE}
    begin
      Result := False;
    {$ENDIF}
    end;
    
    function TCSBroadcastReceiver.Last: String;
    begin
      Result := FItems[FItems.Count];
    end;
    
    procedure TCSBroadcastReceiver.RegisterReceive;
    {$IFDEF ANDROID}
    var
      I: Integer;
    begin
      if FListener = nil then
        FListener := TCSListener.Create(Self);
      if FReceiver = nil then
        FReceiver := TJFMXBroadcastReceiver.JavaClass.init(FListener);
      if FItems <> nil then
        if FItems.Count > 0 then
          for I := 0 to FItems.Count -1 do
            Add(FItems[I]);
    {$ELSE}
    begin
    {$ENDIF}
    end;
    
    function TCSBroadcastReceiver.Remove(const csValue: String): Integer;
    begin
      Result := FItems.IndexOf(csValue);
      if Result > -1 then
        FItems.Delete(Result);
    end;
    
    procedure TCSBroadcastReceiver.SendBroadcast(csValue: String);
    {$IFDEF ANDROID}
    var
      Inx: JIntent;
    begin
      Inx := TJIntent.Create;
      Inx.setAction(StringToJString(csValue));
      TAndroidHelper.Context.sendBroadcast(Inx);
    {$ELSE}
    begin
    {$ENDIF}
    end;
    
    {$IFDEF ANDROID}
    constructor TCSListener.Create(AOwner: TCSBroadcastReceiver);
    begin
      inherited Create;
      FOwner := AOwner;
    end;
    
    procedure TCSListener.OnReceive(csContext: JContext; csIntent: JIntent);
    begin
      if Assigned(FOwner.OnReceive) then
        FOwner.onReceive(csContext, csIntent);
    end;
    
    {$ENDIF}
    
    end.
    

    Secondly, you must create your own definition of JMethod a JLang_Class - you can download it from here.

    unit Androidapi.JNI.JavaTypes.Own;
    
    interface
    
    uses
      Androidapi.JNI.JavaTypes,
      Androidapi.JNIBridge;
    
    type
      JOwnMethod = interface;//java.lang.reflect.Method
      JOwnLang_Class = interface;//java.lang.Class
    
      JOwnMethodClass = interface(JObjectClass)
        ['{C995BD27-1D77-48E5-B478-EB8E9E607020}']
      end;
    
      [JavaSignature('java/lang/reflect/Method')]
      JOwnMethod = interface(JObject)
        ['{ED1B0770-0BD6-4D4A-B801-9D18AB92C834}']
        procedure setAccessible(flag: Boolean); cdecl; overload;
    
        function equals(other: JObject): Boolean; cdecl;
        function getAnnotation(annotationType: JOwnLang_Class): JAnnotation; cdecl;
        function getAnnotations: TJavaObjectArray<JAnnotation>; cdecl;
        function getDeclaredAnnotations: TJavaObjectArray<JAnnotation>; cdecl;
        function getDeclaringClass: JOwnLang_Class; cdecl;
        function getDefaultValue: JObject; cdecl;
        function getExceptionTypes: TJavaObjectArray<JOwnLang_Class>; cdecl;
        function getGenericExceptionTypes: TJavaObjectArray<Jreflect_Type>; cdecl;
        function getGenericParameterTypes: TJavaObjectArray<Jreflect_Type>; cdecl;
        function getGenericReturnType: Jreflect_Type; cdecl;
        function getModifiers: Integer; cdecl;
        function getName: JString; cdecl;
        function getParameterAnnotations: TJavaObjectBiArray<JAnnotation>; cdecl;
        function getParameterTypes: TJavaObjectArray<JOwnLang_Class>; cdecl;
        function getReturnType: JOwnLang_Class; cdecl;
        function getTypeParameters: TJavaObjectArray<JTypeVariable>; cdecl;
        function hashCode: Integer; cdecl;
        function invoke(receiver: JObject; args: TJavaObjectArray<JObject>): JObject; cdecl;
        function isAnnotationPresent(annotationType: JOwnLang_Class): Boolean; cdecl;
        function isBridge: Boolean; cdecl;
        function isSynthetic: Boolean; cdecl;
        function isVarArgs: Boolean; cdecl;
        function toGenericString: JString; cdecl;
        function toString: JString; cdecl;
      end;
      TJOwnMethod = class(TJavaGenericImport<JOwnMethodClass, JOwnMethod>) end;
    
      JOwnLang_ClassClass = interface(JObjectClass)
        ['{E1A7F20A-FD87-4D67-9469-7492FD97D55D}']
        {class} function forName(className: JString): JOwnLang_Class; cdecl; overload;
        {class} function forName(className: JString; shouldInitialize: Boolean; classLoader: JClassLoader): JOwnLang_Class; cdecl; overload;
      end;
    
      [JavaSignature('java/lang/Class')]
      JOwnLang_Class = interface(JObject)
        ['{B056EDE6-77D8-4CDD-9864-147C201FD87C}']
        function asSubclass(c: JOwnLang_Class): JOwnLang_Class; cdecl;
        function cast(obj: JObject): JObject; cdecl;
        function desiredAssertionStatus: Boolean; cdecl;
        function getAnnotation(annotationType: JOwnLang_Class): JAnnotation; cdecl;
        function getAnnotations: TJavaObjectArray<JAnnotation>; cdecl;
        function getCanonicalName: JString; cdecl;
        function getClassLoader: JClassLoader; cdecl;
        function getClasses: TJavaObjectArray<JOwnLang_Class>; cdecl;
        function getComponentType: JOwnLang_Class; cdecl;
        function getConstructors: TJavaObjectArray<JConstructor>; cdecl;
        function getDeclaredAnnotations: TJavaObjectArray<JAnnotation>; cdecl;
        function getDeclaredClasses: TJavaObjectArray<JOwnLang_Class>; cdecl;
        function getDeclaredConstructors: TJavaObjectArray<JConstructor>; cdecl;
        function getDeclaredField(name: JString): JField; cdecl;
        function getDeclaredFields: TJavaObjectArray<JField>; cdecl;
        function getDeclaredMethod(name: JString; parameterTypes: TJavaObjectArray<JOwnLang_Class>): JOwnMethod; cdecl;
        function getDeclaredMethods: TJavaObjectArray<JOwnMethod>; cdecl;
        function getDeclaringClass: JOwnLang_Class; cdecl;
        function getEnclosingClass: JOwnLang_Class; cdecl;
        function getEnclosingConstructor: JConstructor; cdecl;
        function getEnclosingMethod: JOwnMethod; cdecl;
        function getEnumConstants: TJavaObjectArray<JObject>; cdecl;
        function getField(name: JString): JField; cdecl;
        function getFields: TJavaObjectArray<JField>; cdecl;
        function getGenericInterfaces: TJavaObjectArray<Jreflect_Type>; cdecl;
        function getGenericSuperclass: Jreflect_Type; cdecl;
        function getInterfaces: TJavaObjectArray<JOwnLang_Class>; cdecl;
        function getMethods: TJavaObjectArray<JOwnMethod>; cdecl;
        function getModifiers: Integer; cdecl;
        function getName: JString; cdecl;
        function getPackage: JPackage; cdecl;
        //function getProtectionDomain: JProtectionDomain; cdecl;
        //function getResource(resourceName: JString): JURL; cdecl;
        function getResourceAsStream(resourceName: JString): JInputStream; cdecl;
        function getSigners: TJavaObjectArray<JObject>; cdecl;
        function getSimpleName: JString; cdecl;
        function getSuperclass: JOwnLang_Class; cdecl;
        function getTypeParameters: TJavaObjectArray<JTypeVariable>; cdecl;
        function isAnnotation: Boolean; cdecl;
        function isAnnotationPresent(annotationType: JOwnLang_Class): Boolean; cdecl;
        function isAnonymousClass: Boolean; cdecl;
        function isArray: Boolean; cdecl;
        function isAssignableFrom(c: JOwnLang_Class): Boolean; cdecl;
        function isEnum: Boolean; cdecl;
        function isInstance(object_: JObject): Boolean; cdecl;
        function isInterface: Boolean; cdecl;
        function isLocalClass: Boolean; cdecl;
        function isMemberClass: Boolean; cdecl;
        function isPrimitive: Boolean; cdecl;
        function isSynthetic: Boolean; cdecl;
        function newInstance: JObject; cdecl;
        function toString: JString; cdecl;
      end;
      TJOwnLang_Class = class(TJavaGenericImport<JOwnLang_ClassClass, JOwnLang_Class>) end;
    
    implementation
    
    end.
    

    Thirdly, you must enable items PROCESS_OUTGOING_CALL and READ_PHONE_STATE in the Uses Permissions

    Fourth, you must create the code for Form1:

    unit Unit1;
    
    interface
    
    uses
      System.SysUtils,
      System.Types,
      System.UITypes,
      System.Classes,
      System.Variants,
    
      FMX.Types,
      FMX.Controls,
      FMX.Forms,
      FMX.Graphics,
      FMX.Dialogs,
      FMX.Controls.Presentation,
      FMX.ScrollBox,
      FMX.Memo,
    
      CSBroadcastReceiver,
      Androidapi.JNI.JavaTypes.Own,
    
      Androidapi.Jni,
      AndroidApi.JNI.GraphicsContentViewText,
      Androidapi.Jni.JavaTypes,
      Androidapi.JNI.Os,
      Androidapi.JNIBridge,
      Androidapi.JNI.Telephony;
    
    type
      TForm1 = class(TForm)
        Memo1: TMemo;
        procedure FormCreate(Sender: TObject);
        procedure FormClose(Sender: TObject; var Action: TCloseAction);
      private
        { Private declarations }
        procedure CreateBroadcastReceiver;
        procedure BroadcastReceiverOnReceive(csContext: JContext; csIntent: JIntent);
        procedure CheckPhoneCallState(Context: JContext; Intent: JIntent);
        function KillCall(Context: JContext): Boolean;
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
      BroadcastReceiver: TCSBroadcastReceiver;
    
    implementation
    
    uses
      Androidapi.Jni.App, Androidapi.Helpers, Androidapi.Log;
    
    {$R *.fmx}
    
    procedure TForm1.CreateBroadcastReceiver;
    begin
      if not Assigned(BroadcastReceiver) then
        begin
          BroadcastReceiver:= TCSBroadcastReceiver.Create(nil);
          BroadcastReceiver.OnReceive:= BroadcastReceiverOnReceive;
          BroadcastReceiver.RegisterReceive;
          BroadcastReceiver.Add('android.intent.action.PHONE_STATE');
          BroadcastReceiver.Add('android.intent.action.NEW_OUTGOING_CALL');
        end;
    end;
    
    procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      if Assigned(BroadcastReceiver) then
        BroadcastReceiver.Free;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      CreateBroadcastReceiver;
    end;
    
    procedure TForm1.BroadcastReceiverOnReceive(csContext: JContext; csIntent: JIntent);
    begin
      CheckPhoneCallState(csContext, csIntent);
    end;
    
    procedure TForm1.CheckPhoneCallState(Context: JContext; Intent: JIntent);
    var
      telephonyService: JObject;
      telephonyManager: JTelephonyManager;
      state: JString;
      incomingCallNumber: string;
      outgoingCallNumber: string;
      outputResult: string;
    begin
      outputResult:= #13#10;
    
      telephonyService := TAndroidHelper.Context.getSystemService(TJContext.JavaClass.TELEPHONY_SERVICE);
      telephonyManager := TJTelephonyManager.Wrap((telephonyService as ILocalObject).GetObjectID);
    
      if JStringToString(Intent.getAction).Equals('android.intent.action.PHONE_STATE') then
        begin
          state:= Intent.getStringExtra(TJTelephonyManager.JavaClass.EXTRA_STATE);
    
          if state.equals(TJTelephonyManager.JavaClass.EXTRA_STATE_IDLE) then
            outputResult:= outputResult + 'Phone is IDLE ' + #13#10
          else if state.equals(TJTelephonyManager.JavaClass.EXTRA_STATE_RINGING) then
            begin
              incomingCallNumber:= JStringToString(Intent.getStringExtra(TJTelephonyManager.JavaClass.EXTRA_INCOMING_NUMBER));
              if incomingCallNumber.Equals('') then
                incomingCallNumber:= 'PRIVATE NUMBER';
    
              outputResult:= outputResult + 'Phone is RINGING' + #13#10;
              outputResult:= outputResult + 'Incoming call from ' + incomingCallNumber + #13#10;
              if incomingCallNumber = 'xyz' then
                if KillCall(Context) then
                  outputResult:= outputResult + 'Call was terminated' + #13#10
                else
                  outputResult:= outputResult + 'Call was not terminated' + #13#10;
            end
          else if state.equals(TJTelephonyManager.JavaClass.EXTRA_STATE_OFFHOOK) then
            outputResult:= outputResult + 'Phone is OFFHOOK' + #13#10;
        end
      else if JStringToString(Intent.getAction).Equals('android.intent.action.NEW_OUTGOING_CALL') then
        begin
          outgoingCallNumber:= JStringToString(Intent.getStringExtra(TJIntent.JavaClass.EXTRA_PHONE_NUMBER));
          outputResult:= outputResult + 'Outgoing call to ' + outgoingCallNumber + #13#10;
          if outgoingCallNumber = 'xyz' then
            begin
              BroadcastReceiver.SetResultData(nil);
              outputResult:= outputResult + 'Call is not allowed to ' + outgoingCallNumber + #13#10;
            end;
        end;
    
      Memo1.Lines.Append(outputResult);
    end;
    
    function TForm1.KillCall(Context: JContext): Boolean;
    var
      telephonyService: JObject;
      classTelephony: JOwnLang_Class;
      methodGetITelephony: JOwnMethod;
      telephonyInterface: JObject;
      telephonyInterfaceClass: JOwnLang_Class;
      methodEndCall: JOwnMethod;
    begin
      try
        telephonyService:= TAndroidHelper.Context.getSystemService(TJContext.JavaClass.TELEPHONY_SERVICE);
        classTelephony := TJOwnLang_Class.JavaClass.forName(telephonyService.getClass.getName);
        methodGetITelephony:= classTelephony.getDeclaredMethod(StringToJString('getITelephony'), nil);
        methodGetITelephony.setAccessible(True);
        telephonyInterface := methodGetITelephony.invoke(telephonyService, nil);
        telephonyInterfaceClass := TJOwnLang_Class.JavaClass.forName(telephonyInterface.getClass.getName);
        methodEndCall:= telephonyInterfaceClass.getDeclaredMethod(StringToJString('endCall'), nil);
        methodEndCall.invoke(telephonyInterface, nil);
        Result:= True;
      except
        on E: Exception do
        begin
          Result := False;
        end;
      end;
    end;
    end.
    

    Complete demo code you can download here.