2014-11-16 23:14:06 -08:00
/ *
* Copyright 2014 Albert Vaca Cintora < albertvaka @gmail.com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of
* the License or ( at your option ) version 3 or any later version
* accepted by the membership of KDE e . V . ( or its successor approved
* by the membership of KDE e . V . ) , which shall act as a proxy
* defined in Section 14 of version 3 of the license .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
2018-09-29 19:21:41 +02:00
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
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 ;
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
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 ;
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 ;
2015-01-05 21:57:49 -08:00
import java.util.Timer ;
import java.util.TimerTask ;
2018-12-27 15:40:57 +01:00
import androidx.core.content.ContextCompat ;
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 ;
2013-08-16 10:31:01 +02:00
if ( state . equals ( TelephonyManager . EXTRA_STATE_RINGING ) )
intState = TelephonyManager . CALL_STATE_RINGING ;
else if ( state . equals ( TelephonyManager . EXTRA_STATE_OFFHOOK ) )
intState = TelephonyManager . CALL_STATE_OFFHOOK ;
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
public String getDisplayName ( ) {
return context . getResources ( ) . getString ( R . string . pref_plugin_telephony ) ;
}
@Override
public String getDescription ( ) {
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 " ) ;
2018-03-04 11:31:37 +01:00
device . sendPacket ( np ) ;
2013-08-16 10:31:01 +02:00
break ;
case TelephonyManager . CALL_STATE_OFFHOOK : //Ongoing call
np . set ( " event " , " talking " ) ;
2018-03-04 11:31:37 +01:00
device . 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 " ) ;
device . 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
2019-07-04 20:57:04 +02:00
if ( " ringing " . equals ( lastPacket . getString ( " event " , null ) ) ) {
2018-02-22 00:48:55 +01:00
np . set ( " event " , " missedCall " ) ;
2018-03-04 11:31:37 +01:00
np . set ( " phoneNumber " , lastPacket . getString ( " phoneNumber " , null ) ) ;
np . set ( " contactName " , lastPacket . getString ( " contactName " , null ) ) ;
device . 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 ) ;
2018-09-29 19:30:27 +02:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
am . setStreamVolume ( AudioManager . STREAM_RING , AudioManager . ADJUST_UNMUTE , 0 ) ;
} else {
am . setStreamMute ( AudioManager . STREAM_RING , false ) ;
}
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 ) ;
2018-09-16 16:05:01 -06:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
am . setStreamVolume ( AudioManager . STREAM_RING , AudioManager . ADJUST_MUTE , 0 ) ;
} else {
am . setStreamMute ( AudioManager . STREAM_RING , true ) ;
}
isMuted = true ;
}
}
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 ) ;
2018-02-22 00:48:55 +01:00
permissionExplanation = R . string . telephony_permission_explanation ;
optionalPermissionExplanation = R . string . telephony_optional_permission_explanation ;
2013-08-16 10:31:01 +02:00
return true ;
}
@Override
public void onDestroy ( ) {
context . unregisterReceiver ( receiver ) ;
}
@Override
2018-03-04 11:31:37 +01:00
public boolean onPacketReceived ( 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
2018-03-04 11:31:37 +01:00
public 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
2018-03-04 11:31:37 +01:00
public 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
public String [ ] getRequiredPermissions ( ) {
2019-03-18 01:34:51 +00:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
2019-03-19 09:00:38 +01:00
return new String [ ] {
Manifest . permission . READ_PHONE_STATE ,
2019-04-03 16:57:47 +05:30
Manifest . permission . READ_CALL_LOG ,
2019-03-19 09:00:38 +01:00
} ;
2019-03-18 01:34:51 +00:00
} else {
return new String [ 0 ] ;
}
2017-05-31 15:51:07 +02:00
}
2017-07-11 13:50:40 +02:00
@Override
public 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 ;
}
2013-08-16 10:31:01 +02:00
}