2014-11-16 23:14:06 -08:00
/ *
2020-08-17 16:17:20 +02:00
* SPDX - FileCopyrightText : 2014 Albert Vaca Cintora < albertvaka @gmail.com >
2014-11-16 23:14:06 -08:00
*
2020-08-17 16:17:20 +02:00
* SPDX - License - Identifier : GPL - 2 . 0 - only OR GPL - 3 . 0 - only OR LicenseRef - KDE - Accepted - GPL
2018-09-29 19:21:41 +02:00
* /
2014-11-16 23:14:06 -08:00
2013-09-05 01:37:59 +02:00
package org.kde.kdeconnect.Plugins.TelephonyPlugin ;
2013-08-16 10:31:01 +02:00
2017-05-31 15:51:07 +02:00
import android.Manifest ;
2023-03-05 22:03:58 +01:00
import android.app.Activity ;
2013-08-16 10:31:01 +02:00
import android.content.BroadcastReceiver ;
import android.content.Context ;
import android.content.Intent ;
import android.content.IntentFilter ;
2018-02-24 15:49:39 +01:00
import android.content.SharedPreferences ;
2017-05-31 15:51:07 +02:00
import android.content.pm.PackageManager ;
2015-01-05 21:57:49 -08:00
import android.media.AudioManager ;
2016-06-02 13:26:19 +02:00
import android.os.Build ;
2018-02-24 15:49:39 +01:00
import android.preference.PreferenceManager ;
import android.telephony.PhoneNumberUtils ;
2013-08-16 10:31:01 +02:00
import android.telephony.TelephonyManager ;
2020-07-09 17:31:44 +05:30
import android.text.TextUtils ;
2015-09-08 01:13:39 -07:00
import android.util.Log ;
2013-08-16 10:31:01 +02:00
2023-05-26 22:19:21 +02:00
import androidx.annotation.NonNull ;
2023-03-05 22:03:58 +01:00
import androidx.core.content.ContextCompat ;
2020-07-12 13:37:24 +05:30
import org.apache.commons.lang3.ArrayUtils ;
2013-09-05 01:37:59 +02:00
import org.kde.kdeconnect.Helpers.ContactsHelper ;
2018-03-04 11:31:37 +01:00
import org.kde.kdeconnect.NetworkPacket ;
2013-09-05 01:37:59 +02:00
import org.kde.kdeconnect.Plugins.Plugin ;
2019-02-11 20:04:40 +01:00
import org.kde.kdeconnect.Plugins.PluginFactory ;
2022-12-28 19:24:07 +01:00
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment ;
2013-09-05 01:35:12 +02:00
import org.kde.kdeconnect_tp.R ;
2013-08-16 10:31:01 +02:00
2016-03-06 21:39:52 +01:00
import java.util.Map ;
2024-04-03 19:22:29 +00:00
import java.util.Objects ;
2015-01-05 21:57:49 -08:00
import java.util.Timer ;
import java.util.TimerTask ;
2019-02-11 20:04:40 +01:00
@PluginFactory.LoadablePlugin
2013-08-16 10:31:01 +02:00
public class TelephonyPlugin extends Plugin {
2018-08-15 17:12:36 +02:00
2018-05-23 23:38:27 -06:00
/ * *
2019-03-19 09:00:38 +01:00
* Packet used for simple call events
2018-09-29 19:21:41 +02:00
* < p >
2018-05-23 23:38:27 -06:00
* It contains the key " event " which maps to a string indicating the type of event :
2018-09-29 19:21:41 +02:00
* - " ringing " - A phone call is incoming
* - " missedCall " - An incoming call was not answered
2019-04-03 16:57:47 +05:30
* - " sms " - An incoming SMS message
* - Note : As of this writing ( 15 May 2018 ) the SMS interface is being improved and this type of event
* is no longer the preferred way of handling SMS . Use the packets defined by the SMS plugin instead .
2018-09-29 19:21:41 +02:00
* < p >
* Depending on the event , other fields may be defined
2018-05-23 23:38:27 -06:00
* /
2018-08-15 17:12:36 +02:00
public final static String PACKET_TYPE_TELEPHONY = " kdeconnect.telephony " ;
2018-09-16 16:05:01 -06:00
/ * *
* Old - style packet sent to request a simple telephony action
2018-09-29 19:21:41 +02:00
* < p >
2019-04-03 16:57:47 +05:30
* The events handled were :
* - to request the device to mute its ringer
* - to request an SMS to be sent .
* < p >
* In case an SMS was being requested , the body was like so :
* { " sendSms " : true ,
* " phoneNumber " : " 542904563213 " ,
* " messageBody " : " Hi mom! "
* }
2018-09-29 19:21:41 +02:00
* < p >
2018-09-16 16:05:01 -06:00
* In case a ringer muted was requested , the body looked like so :
* { " action " : " mute " }
2018-09-29 19:21:41 +02:00
* < p >
2018-09-16 16:05:01 -06:00
* Ringer mute requests are best handled by PACKET_TYPE_TELEPHONY_REQUEST_MUTE
2018-09-29 19:21:41 +02:00
* < p >
2018-09-16 16:05:01 -06:00
* This packet type is retained for backwards - compatibility with old desktop applications ,
* but support should be dropped once those applications are no longer supported . New
* applications should not use this packet type .
* /
@Deprecated
2018-03-04 11:31:37 +01:00
public final static String PACKET_TYPE_TELEPHONY_REQUEST = " kdeconnect.telephony.request " ;
2018-05-23 23:38:27 -06:00
2018-09-16 16:05:01 -06:00
/ * *
* Packet sent to indicate the user has requested the device mute its ringer
2018-09-29 19:21:41 +02:00
* < p >
2018-09-16 16:05:01 -06:00
* The body should be empty
* /
2018-09-29 19:33:36 +02:00
private final static String PACKET_TYPE_TELEPHONY_REQUEST_MUTE = " kdeconnect.telephony.request_mute " ;
2018-09-16 16:05:01 -06:00
2018-08-15 17:12:36 +02:00
private static final String KEY_PREF_BLOCKED_NUMBERS = " telephony_blocked_numbers " ;
2015-01-05 21:57:49 -08:00
private int lastState = TelephonyManager . CALL_STATE_IDLE ;
2018-03-04 11:31:37 +01:00
private NetworkPacket lastPacket = null ;
2015-01-31 00:16:06 -08:00
private boolean isMuted = false ;
2015-01-05 21:57:49 -08:00
2014-03-29 01:47:15 +01:00
private final BroadcastReceiver receiver = new BroadcastReceiver ( ) {
2013-08-16 10:31:01 +02:00
@Override
public void onReceive ( Context context , Intent intent ) {
String action = intent . getAction ( ) ;
2013-09-05 10:48:42 +02:00
//Log.e("TelephonyPlugin","Telephony event: " + action);
2013-08-16 10:31:01 +02:00
2018-08-15 17:12:36 +02:00
if ( TelephonyManager . ACTION_PHONE_STATE_CHANGED . equals ( action ) ) {
2013-08-16 10:31:01 +02:00
String state = intent . getStringExtra ( TelephonyManager . EXTRA_STATE ) ;
2018-02-22 00:48:55 +01:00
int intState = TelephonyManager . CALL_STATE_IDLE ;
2024-05-12 16:06:38 +02:00
if ( TelephonyManager . EXTRA_STATE_RINGING . equals ( state ) ) {
2013-08-16 10:31:01 +02:00
intState = TelephonyManager . CALL_STATE_RINGING ;
2024-05-12 16:06:38 +02:00
} else if ( TelephonyManager . EXTRA_STATE_OFFHOOK . equals ( state ) ) {
2013-08-16 10:31:01 +02:00
intState = TelephonyManager . CALL_STATE_OFFHOOK ;
2024-05-12 16:06:38 +02:00
}
2013-08-16 10:31:01 +02:00
2019-03-21 23:13:27 +00:00
// We will get a second broadcast with the phone number https://developer.android.com/reference/android/telephony/TelephonyManager#ACTION_PHONE_STATE_CHANGED
if ( ! intent . hasExtra ( TelephonyManager . EXTRA_INCOMING_NUMBER ) )
return ;
String number = intent . getStringExtra ( TelephonyManager . EXTRA_INCOMING_NUMBER ) ;
2013-08-16 10:31:01 +02:00
2019-07-04 20:57:04 +02:00
if ( intState ! = lastState ) {
lastState = intState ;
callBroadcastReceived ( intState , number ) ;
2018-09-30 19:23:39 +02:00
}
2013-08-16 10:31:01 +02:00
}
}
} ;
2018-02-22 00:48:55 +01:00
@Override
2023-05-26 22:19:21 +02:00
public @NonNull String getDisplayName ( ) {
2018-02-22 00:48:55 +01:00
return context . getResources ( ) . getString ( R . string . pref_plugin_telephony ) ;
}
@Override
2023-05-26 22:19:21 +02:00
public @NonNull String getDescription ( ) {
2018-02-22 00:48:55 +01:00
return context . getResources ( ) . getString ( R . string . pref_plugin_telephony_desc ) ;
}
2013-08-16 10:31:01 +02:00
2018-02-22 00:48:55 +01:00
private void callBroadcastReceived ( int state , String phoneNumber ) {
2013-11-06 21:13:37 +01:00
2018-02-24 15:49:39 +01:00
if ( isNumberBlocked ( phoneNumber ) )
return ;
2018-03-04 11:31:37 +01:00
NetworkPacket np = new NetworkPacket ( PACKET_TYPE_TELEPHONY ) ;
2016-06-01 12:58:10 +02:00
2017-05-31 15:51:07 +02:00
int permissionCheck = ContextCompat . checkSelfPermission ( context ,
Manifest . permission . READ_CONTACTS ) ;
2016-03-06 21:50:58 +01:00
2018-02-22 00:48:55 +01:00
if ( permissionCheck = = PackageManager . PERMISSION_GRANTED ) {
2017-05-31 15:51:07 +02:00
Map < String , String > contactInfo = ContactsHelper . phoneNumberLookup ( context , phoneNumber ) ;
2016-03-06 21:50:58 +01:00
2017-05-31 15:51:07 +02:00
if ( contactInfo . containsKey ( " name " ) ) {
np . set ( " contactName " , contactInfo . get ( " name " ) ) ;
}
if ( contactInfo . containsKey ( " photoID " ) ) {
String photoUri = contactInfo . get ( " photoID " ) ;
if ( photoUri ! = null ) {
try {
String base64photo = ContactsHelper . photoId64Encoded ( context , photoUri ) ;
2020-07-09 17:31:44 +05:30
if ( ! TextUtils . isEmpty ( base64photo ) ) {
2017-05-31 15:51:07 +02:00
np . set ( " phoneThumbnail " , base64photo ) ;
}
} catch ( Exception e ) {
Log . e ( " TelephonyPlugin " , " Failed to get contact photo " ) ;
2016-06-03 14:51:31 +02:00
}
}
2017-05-31 15:51:07 +02:00
2016-06-03 14:51:31 +02:00
}
2017-05-31 15:51:07 +02:00
} else {
2018-02-22 00:48:55 +01:00
np . set ( " contactName " , phoneNumber ) ;
2017-05-31 15:51:07 +02:00
}
if ( phoneNumber ! = null ) {
np . set ( " phoneNumber " , phoneNumber ) ;
2013-08-16 10:31:01 +02:00
}
switch ( state ) {
case TelephonyManager . CALL_STATE_RINGING :
2018-09-29 19:30:27 +02:00
unmuteRinger ( ) ;
2013-08-16 10:31:01 +02:00
np . set ( " event " , " ringing " ) ;
2024-04-03 19:22:29 +00:00
getDevice ( ) . sendPacket ( np ) ;
2013-08-16 10:31:01 +02:00
break ;
case TelephonyManager . CALL_STATE_OFFHOOK : //Ongoing call
np . set ( " event " , " talking " ) ;
2024-04-03 19:22:29 +00:00
getDevice ( ) . sendPacket ( np ) ;
2013-08-16 10:31:01 +02:00
break ;
case TelephonyManager . CALL_STATE_IDLE :
2018-09-30 19:23:39 +02:00
if ( lastPacket ! = null ) {
2013-08-16 10:31:01 +02:00
//Resend a cancel of the last event (can either be "ringing" or "talking")
2018-03-04 11:31:37 +01:00
lastPacket . set ( " isCancel " , " true " ) ;
2024-04-03 19:22:29 +00:00
getDevice ( ) . sendPacket ( lastPacket ) ;
2013-08-16 10:31:01 +02:00
2015-01-05 21:57:49 -08:00
if ( isMuted ) {
Timer timer = new Timer ( ) ;
timer . schedule ( new TimerTask ( ) {
@Override
public void run ( ) {
2018-09-29 19:30:27 +02:00
unmuteRinger ( ) ;
2015-01-05 21:57:49 -08:00
}
} , 500 ) ;
}
2013-08-16 10:31:01 +02:00
//Emit a missed call notification if needed
2024-04-11 22:54:48 +02:00
if ( " ringing " . equals ( lastPacket . getString ( " event " ) ) ) {
2018-02-22 00:48:55 +01:00
np . set ( " event " , " missedCall " ) ;
2024-04-11 22:54:48 +02:00
np . set ( " phoneNumber " , lastPacket . getStringOrNull ( " phoneNumber " ) ) ;
np . set ( " contactName " , lastPacket . getStringOrNull ( " contactName " ) ) ;
2024-04-03 19:22:29 +00:00
getDevice ( ) . sendPacket ( np ) ;
2013-08-16 10:31:01 +02:00
}
}
break ;
}
2018-03-04 11:31:37 +01:00
lastPacket = np ;
2013-08-16 10:31:01 +02:00
}
2018-09-29 19:30:27 +02:00
private void unmuteRinger ( ) {
if ( isMuted ) {
2020-07-07 16:45:02 +05:30
AudioManager am = ContextCompat . getSystemService ( context , AudioManager . class ) ;
2024-05-12 16:25:11 +02:00
am . setStreamVolume ( AudioManager . STREAM_RING , AudioManager . ADJUST_UNMUTE , 0 ) ;
2018-09-29 19:30:27 +02:00
isMuted = false ;
}
}
2018-09-16 16:05:01 -06:00
private void muteRinger ( ) {
if ( ! isMuted ) {
2020-07-07 16:45:02 +05:30
AudioManager am = ContextCompat . getSystemService ( context , AudioManager . class ) ;
2024-05-12 16:25:11 +02:00
am . setStreamVolume ( AudioManager . STREAM_RING , AudioManager . ADJUST_MUTE , 0 ) ;
2018-09-16 16:05:01 -06:00
isMuted = true ;
}
}
2023-03-29 19:01:57 +02:00
@Override
protected int getPermissionExplanation ( ) {
return R . string . telephony_permission_explanation ;
}
@Override
protected int getOptionalPermissionExplanation ( ) {
return R . string . telephony_optional_permission_explanation ;
}
2013-08-16 10:31:01 +02:00
@Override
public boolean onCreate ( ) {
2019-03-19 09:00:38 +01:00
IntentFilter filter = new IntentFilter ( TelephonyManager . ACTION_PHONE_STATE_CHANGED ) ;
2015-04-11 23:43:00 -07:00
filter . setPriority ( 500 ) ;
2013-08-16 10:31:01 +02:00
context . registerReceiver ( receiver , filter ) ;
return true ;
}
@Override
public void onDestroy ( ) {
context . unregisterReceiver ( receiver ) ;
}
@Override
2023-05-26 22:19:21 +02:00
public boolean onPacketReceived ( @NonNull NetworkPacket np ) {
2018-08-15 17:12:36 +02:00
2018-09-16 16:05:01 -06:00
switch ( np . getType ( ) ) {
case PACKET_TYPE_TELEPHONY_REQUEST :
if ( np . getString ( " action " ) . equals ( " mute " ) ) {
muteRinger ( ) ;
2016-06-02 13:26:19 +02:00
}
2018-09-16 16:05:01 -06:00
break ;
case PACKET_TYPE_TELEPHONY_REQUEST_MUTE :
muteRinger ( ) ;
break ;
2015-01-05 21:57:49 -08:00
}
return true ;
2013-08-16 10:31:01 +02:00
}
2018-02-24 15:49:39 +01:00
private boolean isNumberBlocked ( String number ) {
SharedPreferences sharedPref = PreferenceManager . getDefaultSharedPreferences ( context ) ;
String [ ] blockedNumbers = sharedPref . getString ( KEY_PREF_BLOCKED_NUMBERS , " " ) . split ( " \ n " ) ;
2018-03-03 16:06:52 +01:00
for ( String s : blockedNumbers ) {
2018-02-24 15:49:39 +01:00
if ( PhoneNumberUtils . compare ( number , s ) )
return true ;
}
return false ;
}
2015-09-08 14:54:04 -07:00
@Override
2023-05-26 22:19:21 +02:00
public @NonNull String [ ] getSupportedPacketTypes ( ) {
2018-05-23 23:38:27 -06:00
return new String [ ] {
2018-09-29 19:21:41 +02:00
PACKET_TYPE_TELEPHONY_REQUEST ,
PACKET_TYPE_TELEPHONY_REQUEST_MUTE ,
2018-05-23 23:38:27 -06:00
} ;
2015-09-08 14:54:04 -07:00
}
@Override
2023-05-26 22:19:21 +02:00
public @NonNull String [ ] getOutgoingPacketTypes ( ) {
2018-05-23 23:38:27 -06:00
return new String [ ] {
2018-08-15 17:12:36 +02:00
PACKET_TYPE_TELEPHONY
2018-05-23 23:38:27 -06:00
} ;
2015-09-08 14:54:04 -07:00
}
2017-05-31 15:51:07 +02:00
@Override
2023-05-26 22:19:21 +02:00
public @NonNull String [ ] getRequiredPermissions ( ) {
2024-05-12 16:25:11 +02:00
return new String [ ] {
Manifest . permission . READ_PHONE_STATE ,
Manifest . permission . READ_CALL_LOG ,
} ;
2017-05-31 15:51:07 +02:00
}
2017-07-11 13:50:40 +02:00
@Override
2023-05-26 22:19:21 +02:00
public @NonNull String [ ] getOptionalPermissions ( ) {
2019-04-03 16:57:47 +05:30
return new String [ ] {
Manifest . permission . READ_CONTACTS ,
} ;
2017-07-11 13:50:40 +02:00
}
2018-02-24 15:49:39 +01:00
@Override
public boolean hasSettings ( ) {
return true ;
}
2022-12-28 19:24:07 +01:00
@Override
2024-05-12 16:06:38 +02:00
public PluginSettingsFragment getSettingsFragment ( @NonNull Activity activity ) {
2022-12-28 19:24:07 +01:00
return PluginSettingsFragment . newInstance ( getPluginKey ( ) , R . xml . telephonyplugin_preferences ) ;
}
2013-08-16 10:31:01 +02:00
}