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
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
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;
|
2013-08-16 10:31:01 +02:00
|
|
|
import android.os.Bundle;
|
2018-02-24 15:49:39 +01:00
|
|
|
import android.preference.PreferenceManager;
|
2017-05-31 15:51:07 +02:00
|
|
|
import android.support.v4.content.ContextCompat;
|
2018-02-24 15:49:39 +01:00
|
|
|
import android.telephony.PhoneNumberUtils;
|
2013-08-16 10:31:01 +02:00
|
|
|
import android.telephony.SmsMessage;
|
|
|
|
import android.telephony.TelephonyManager;
|
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;
|
2017-05-16 21:02:16 +02:00
|
|
|
import org.kde.kdeconnect_tp.BuildConfig;
|
2013-09-05 01:35:12 +02:00
|
|
|
import org.kde.kdeconnect_tp.R;
|
2013-08-16 10:31:01 +02:00
|
|
|
|
2017-05-16 21:02:16 +02:00
|
|
|
import java.util.ArrayList;
|
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;
|
|
|
|
|
2013-08-16 10:31:01 +02:00
|
|
|
public class TelephonyPlugin extends Plugin {
|
|
|
|
|
2018-03-04 11:31:37 +01:00
|
|
|
private final static String PACKET_TYPE_TELEPHONY = "kdeconnect.telephony";
|
|
|
|
public final static String PACKET_TYPE_TELEPHONY_REQUEST = "kdeconnect.telephony.request";
|
2018-02-24 15:49:39 +01:00
|
|
|
private static final String KEY_PREF_BLOCKED_NUMBERS = "telephony_blocked_numbers";
|
2016-05-31 17:19:39 +02:00
|
|
|
|
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-02-22 00:48:55 +01:00
|
|
|
if ("android.provider.Telephony.SMS_RECEIVED".equals(action)) {
|
2013-08-16 10:31:01 +02:00
|
|
|
|
|
|
|
final Bundle bundle = intent.getExtras();
|
|
|
|
if (bundle == null) return;
|
|
|
|
final Object[] pdus = (Object[]) bundle.get("pdus");
|
2018-02-22 00:48:55 +01:00
|
|
|
ArrayList<SmsMessage> messages = new ArrayList<>();
|
2017-05-16 21:02:16 +02:00
|
|
|
|
2013-08-16 10:31:01 +02:00
|
|
|
for (Object pdu : pdus) {
|
2017-05-16 21:02:16 +02:00
|
|
|
// I hope, but am not sure, that the pdus array is in the order that the parts
|
|
|
|
// of the SMS message should be
|
2018-02-22 00:48:55 +01:00
|
|
|
// If it is not, I believe the pdu contains the information necessary to put it
|
2017-05-16 21:02:16 +02:00
|
|
|
// in order, but in my testing the order seems to be correct, so I won't worry
|
|
|
|
// about it now.
|
2018-02-22 00:48:55 +01:00
|
|
|
messages.add(SmsMessage.createFromPdu((byte[]) pdu));
|
2013-08-16 10:31:01 +02:00
|
|
|
}
|
|
|
|
|
2017-05-16 21:02:16 +02:00
|
|
|
smsBroadcastReceived(messages);
|
|
|
|
|
2013-08-16 10:31:01 +02:00
|
|
|
} else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
|
2018-02-22 00:48:55 +01:00
|
|
|
if (number == null)
|
|
|
|
number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
|
2013-08-16 10:31:01 +02:00
|
|
|
|
|
|
|
final int finalIntState = intState;
|
|
|
|
final String finalNumber = number;
|
|
|
|
|
|
|
|
callBroadcastReceived(finalIntState, finalNumber);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
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);
|
|
|
|
if (base64photo != null && !base64photo.isEmpty()) {
|
|
|
|
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:
|
2015-01-05 21:57:49 -08:00
|
|
|
if (isMuted) {
|
|
|
|
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
2016-06-02 13:26:19 +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);
|
|
|
|
}
|
2015-01-05 21:57:49 -08:00
|
|
|
isMuted = false;
|
|
|
|
}
|
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-03-04 11:31:37 +01:00
|
|
|
if (lastState != TelephonyManager.CALL_STATE_IDLE && 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() {
|
|
|
|
if (isMuted) {
|
|
|
|
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
2016-06-02 13:26:19 +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);
|
|
|
|
}
|
2015-01-05 21:57:49 -08:00
|
|
|
isMuted = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 500);
|
|
|
|
}
|
|
|
|
|
2013-08-16 10:31:01 +02:00
|
|
|
//Emit a missed call notification if needed
|
|
|
|
if (lastState == TelephonyManager.CALL_STATE_RINGING) {
|
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
|
|
|
lastState = state;
|
|
|
|
}
|
|
|
|
|
2017-05-16 21:02:16 +02:00
|
|
|
private void smsBroadcastReceived(ArrayList<SmsMessage> messages) {
|
|
|
|
|
|
|
|
if (BuildConfig.DEBUG) {
|
2018-02-22 00:48:55 +01:00
|
|
|
if (!(messages.size() > 0)) {
|
2017-05-16 21:02:16 +02:00
|
|
|
throw new AssertionError("This method requires at least one message");
|
|
|
|
}
|
|
|
|
}
|
2013-08-16 10:31:01 +02:00
|
|
|
|
2018-03-04 11:31:37 +01:00
|
|
|
NetworkPacket np = new NetworkPacket(PACKET_TYPE_TELEPHONY);
|
2013-08-16 10:31:01 +02:00
|
|
|
|
2018-02-22 00:48:55 +01:00
|
|
|
np.set("event", "sms");
|
2013-08-16 10:31:01 +02:00
|
|
|
|
2018-01-17 00:05:57 +01:00
|
|
|
StringBuilder messageBody = new StringBuilder();
|
2018-02-22 00:48:55 +01:00
|
|
|
for (int index = 0; index < messages.size(); index++) {
|
2018-01-17 00:05:57 +01:00
|
|
|
messageBody.append(messages.get(index).getMessageBody());
|
2013-08-16 10:31:01 +02:00
|
|
|
}
|
2018-01-17 00:05:57 +01:00
|
|
|
np.set("messageBody", messageBody.toString());
|
2013-08-16 10:31:01 +02:00
|
|
|
|
2017-05-16 21:02:16 +02:00
|
|
|
String phoneNumber = messages.get(0).getOriginatingAddress();
|
2017-05-31 15:51:07 +02:00
|
|
|
|
2018-02-24 15:49:39 +01:00
|
|
|
if (isNumberBlocked(phoneNumber))
|
|
|
|
return;
|
|
|
|
|
2017-05-31 15:51:07 +02:00
|
|
|
int permissionCheck = ContextCompat.checkSelfPermission(context,
|
|
|
|
Manifest.permission.READ_CONTACTS);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (contactInfo.containsKey("name")) {
|
|
|
|
np.set("contactName", contactInfo.get("name"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (contactInfo.containsKey("photoID")) {
|
|
|
|
np.set("phoneThumbnail", ContactsHelper.photoId64Encoded(context, contactInfo.get("photoID")));
|
|
|
|
}
|
|
|
|
}
|
2013-08-16 10:31:01 +02:00
|
|
|
if (phoneNumber != null) {
|
2015-09-08 01:13:39 -07:00
|
|
|
np.set("phoneNumber", phoneNumber);
|
2016-03-06 21:56:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-04 11:31:37 +01:00
|
|
|
device.sendPacket(np);
|
2013-08-16 10:31:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onCreate() {
|
|
|
|
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
|
2015-04-11 23:43:00 -07:00
|
|
|
filter.setPriority(500);
|
2013-08-16 10:31:01 +02:00
|
|
|
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
|
|
|
|
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) {
|
2015-01-05 21:57:49 -08:00
|
|
|
if (np.getString("action").equals("mute")) {
|
|
|
|
if (!isMuted) {
|
|
|
|
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
2016-06-02 13:26:19 +02: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);
|
|
|
|
}
|
2015-01-05 21:57:49 -08:00
|
|
|
isMuted = true;
|
|
|
|
}
|
|
|
|
}
|
2013-08-16 10:31:01 +02:00
|
|
|
//Do nothing
|
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() {
|
|
|
|
return new String[]{PACKET_TYPE_TELEPHONY_REQUEST};
|
2015-09-08 14:54:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2018-03-04 11:31:37 +01:00
|
|
|
public String[] getOutgoingPacketTypes() {
|
|
|
|
return new String[]{PACKET_TYPE_TELEPHONY};
|
2015-09-08 14:54:04 -07:00
|
|
|
}
|
|
|
|
|
2017-05-31 15:51:07 +02:00
|
|
|
@Override
|
|
|
|
public String[] getRequiredPermissions() {
|
2017-07-11 13:50:40 +02:00
|
|
|
return new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_SMS};
|
2017-05-31 15:51:07 +02:00
|
|
|
}
|
|
|
|
|
2017-07-11 13:50:40 +02:00
|
|
|
@Override
|
|
|
|
public String[] getOptionalPermissions() {
|
|
|
|
return new String[]{Manifest.permission.READ_CONTACTS};
|
|
|
|
}
|
2018-02-24 15:49:39 +01:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean hasSettings() {
|
|
|
|
return true;
|
|
|
|
}
|
2013-08-16 10:31:01 +02:00
|
|
|
}
|