2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 18:07:55 +00:00

Added MMS support to the SMSPlugin using Klinker library.

This commit is contained in:
Aniket Kumar 2020-07-05 13:32:44 +05:30
parent edbf3ccaab
commit b119de8e76
17 changed files with 1023 additions and 31 deletions

View File

@ -22,6 +22,8 @@
<!-- <uses-permission android:name="android.permission.BLUETOOTH" /> --> <!-- <uses-permission android:name="android.permission.BLUETOOTH" /> -->
<!-- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> --> <!-- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
@ -31,9 +33,13 @@
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
@ -49,6 +55,67 @@
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/KdeConnectTheme" android:theme="@style/KdeConnectTheme"
android:name="org.kde.kdeconnect.MyApplication"> android:name="org.kde.kdeconnect.MyApplication">
<receiver
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.SmsReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
<intent-filter android:priority="999">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<receiver
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.SmsSentReceiver"
android:exported="true"
android:taskAffinity="${applicationId}.SMS_SENT" />
<receiver
android:name="com.android.mms.transaction.PushReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<receiver
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.DelegatingMmsReceivedReceiver"
android:enabled="true"
android:exported="true"
android:taskAffinity="com.klinker.android.messaging.MMS_RECEIVED" />
<receiver
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.MmsSentReceiver"
android:exported="true"
android:enabled="true"
android:taskAffinity="com.klinker.android.messaging.MMS_SENT" />
<service
android:name="org.kde.kdeconnect.Plugins.SMSPlugin.HeadlessSmsSendService"
android:exported="true"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
<service
android:name="com.android.mms.transaction.TransactionService"
android:enabled="true"
android:exported="true" />
<service <service
android:name="org.kde.kdeconnect.BackgroundService" android:name="org.kde.kdeconnect.BackgroundService"
android:enabled="true" /> android:enabled="true" />
@ -69,10 +136,29 @@
android:name="org.kde.kdeconnect.UserInterface.MainActivity" android:name="org.kde.kdeconnect.UserInterface.MainActivity"
android:label="KDE Connect" android:label="KDE Connect"
android:theme="@style/KdeConnectTheme.NoActionBar"> android:theme="@style/KdeConnectTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.app.role.SMS"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
@ -314,4 +400,4 @@
</application> </application>
</manifest> </manifest>

View File

@ -165,6 +165,8 @@ dependencies {
implementation 'org.atteo.classindex:classindex:3.6' implementation 'org.atteo.classindex:classindex:3.6'
annotationProcessor 'org.atteo.classindex:classindex:3.6' annotationProcessor 'org.atteo.classindex:classindex:3.6'
implementation 'com.klinkerapps:android-smsmms:5.2.6' //For SMS and MMS purposes
// Testing // Testing
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation 'org.powermock:powermock-core:2.0.0' testImplementation 'org.powermock:powermock-core:2.0.0'

4
proguard-rules.pro vendored
View File

@ -44,3 +44,7 @@
-dontwarn android.test.** -dontwarn android.test.**
-dontwarn java.lang.management.** -dontwarn java.lang.management.**
-dontwarn javax.** -dontwarn javax.**
-dontwarn android.net.ConnectivityManager
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn android.net.LinkProperties

View File

@ -265,6 +265,8 @@
<string name="no_file_browser">There are no file browsers installed.</string> <string name="no_file_browser">There are no file browsers installed.</string>
<string name="pref_plugin_telepathy">Send SMS</string> <string name="pref_plugin_telepathy">Send SMS</string>
<string name="pref_plugin_telepathy_desc">Send text messages from your desktop</string> <string name="pref_plugin_telepathy_desc">Send text messages from your desktop</string>
<string name="pref_plugin_telepathy_mms">Send MMS</string>
<string name="pref_plugin_telepathy_mms_desc">To be able to send MMS from KDE Connect you need to set it as the default SMS app.</string>
<string name="findmyphone_title">Find my phone</string> <string name="findmyphone_title">Find my phone</string>
<string name="findmyphone_title_tablet">Find my tablet</string> <string name="findmyphone_title_tablet">Find my tablet</string>
<string name="findmyphone_title_tv">Find my TV</string> <string name="findmyphone_title_tv">Find my TV</string>
@ -372,6 +374,38 @@
<string name="bigscreen_optional_permission_explanation">To share microphone input from your phone you need to give access to the phone\'s audio input</string> <string name="bigscreen_optional_permission_explanation">To share microphone input from your phone you need to give access to the phone\'s audio input</string>
<string name="bigscreen_speech_extra_prompt">Speech</string> <string name="bigscreen_speech_extra_prompt">Speech</string>
<string name="set_default_sms_app_title">Send MMS</string>
<string name="set_group_message_as_mms_title">Send group MMS</string>
<string name="set_group_message_as_mms" translatable="false">set_group_message_as_mms</string>
<string name="set_long_text_as_mms_title">Send long text as MMS</string>
<string name="set_long_text_as_mms" translatable="false">set_long_text_as_mms</string>
<string name="convert_to_mms_after_title">Convert to MMS</string>
<string name="convert_to_mms_after" translatable="false">convert_to_mms_after</string>
<string name="sms_pref_set_mmsc_dialog_desc">Set MMSC</string>
<string name="sms_pref_set_mmsc_title">MMSC</string>
<string name="sms_pref_set_mmsc" translatable="false">sms_pref_set_mmsc</string>
<string name="sms_pref_set_mms_proxy_dialog_desc">Set MMS proxy</string>
<string name="sms_pref_set_mms_proxy_title">MMS proxy</string>
<string name="sms_pref_set_mms_proxy" translatable="false">sms_pref_set_mms_proxy</string>
<string name="sms_pref_set_mms_port_dialog_desc">Set MMS port</string>
<string name="sms_pref_set_mms_port_title">MMS port</string>
<string name="sms_pref_set_mms_port" translatable="false">sms_pref_set_mms_port</string>
<string name="convert_to_mms_after_default" translatable="false">3</string>
<string-array name="convert_to_mms_after_entries">
<item>After one message</item>
<item>After two messages</item>
<item>After three messages</item>
<item>After four messages</item>
<item>After five messages</item>
</string-array>
<string-array name="convert_to_mms_after_values" translatable="false">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
<string name="theme_dialog_title">Choose theme</string> <string name="theme_dialog_title">Choose theme</string>
<string-array name="theme_list"> <string-array name="theme_list">
<item>Set by Battery Saver</item> <item>Set by Battery Saver</item>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:keep="@xml/smsplugin_preferences">
<EditTextPreference
android:dialogMessage="@string/sms_pref_set_mmsc_dialog_desc"
android:key="@string/sms_pref_set_mmsc"
android:title="@string/sms_pref_set_mmsc_title"
android:defaultValue="Not set"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
android:dialogMessage="@string/sms_pref_set_mms_proxy_dialog_desc"
android:key="@string/sms_pref_set_mms_proxy"
android:title="@string/sms_pref_set_mms_proxy_title"
android:defaultValue="Not set"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
android:dialogMessage="@string/sms_pref_set_mms_port_dialog_desc"
android:key="@string/sms_pref_set_mms_port"
android:title="@string/sms_pref_set_mms_port_title"
android:defaultValue="Not set"
app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:id="@+id/group_message_preference"
android:defaultValue="true"
android:key="@string/set_group_message_as_mms"
android:title="@string/set_group_message_as_mms_title" />
<CheckBoxPreference
android:id="@+id/long_text_message_preference"
android:defaultValue="false"
android:key="@string/set_long_text_as_mms"
android:title="@string/set_long_text_as_mms_title" />
<ListPreference
android:id="@+id/convert_to_mms_after_preference"
android:defaultValue="@string/convert_to_mms_after_default"
android:entries="@array/convert_to_mms_after_entries"
android:entryValues="@array/convert_to_mms_after_values"
android:key="@string/convert_to_mms_after"
android:summary="%s"
android:title="@string/convert_to_mms_after_title" />
</PreferenceScreen>

View File

@ -61,6 +61,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.klinker.android.send_message.Utils;
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
public class SMSHelper { public class SMSHelper {
@ -295,6 +297,13 @@ public class SMSHelper {
// of any MMSes // of any MMSes
List<String> userPhoneNumbers = TelephonyHelper.getAllPhoneNumbers(context); List<String> userPhoneNumbers = TelephonyHelper.getAllPhoneNumbers(context);
if (Utils.isDefaultSmsApp(context)) {
// Due to some reason, which I'm not able to find out yet, when message sending fails, no sent receiver
// gets invoked to mark the message as failed to send. This is the reason we have to delete the failed
// messages pending in the outbox before fetching new messages from the database
deleteFailedMessages(uri, context, fetchColumns, selection, selectionArgs, sortOrder);
}
try (Cursor myCursor = context.getContentResolver().query( try (Cursor myCursor = context.getContentResolver().query(
uri, uri,
fetchColumns.toArray(new String[]{}), fetchColumns.toArray(new String[]{}),
@ -376,6 +385,83 @@ public class SMSHelper {
return toReturn; return toReturn;
} }
/**
* Deletes messages which are failed to send due to some reason
*
* @param uri Uri indicating the messages database to read
* @param context android.content.Context running the request.
* @param fetchColumns List of columns to fetch
* @param selection Parameterizable filter to use with the ContentResolver query. May be null.
* @param selectionArgs Parameters for selection. May be null.
* @param sortOrder Sort ordering passed to Android's content resolver. May be null for unspecified
*/
private static void deleteFailedMessages(
@NonNull Uri uri,
@NonNull Context context,
@NonNull Collection<String> fetchColumns,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder
) {
try (Cursor myCursor = context.getContentResolver().query(
uri,
fetchColumns.toArray(new String[]{}),
selection,
selectionArgs,
sortOrder)
) {
if (myCursor != null && myCursor.moveToFirst()) {
do {
String id = null;
String type = null;
String msgBox = null;
for (int columnIdx = 0; columnIdx < myCursor.getColumnCount(); columnIdx++) {
String colName = myCursor.getColumnName(columnIdx);
if (colName.equals("_id")) {
id = myCursor.getString(columnIdx);
}
if(colName.equals("type")) {
type = myCursor.getString(columnIdx);
}
if (colName.equals("msg_box")) {
msgBox = myCursor.getString(columnIdx);
}
}
if (type != null && id != null) {
if (type.equals(Telephony.Sms.MESSAGE_TYPE_OUTBOX) || type.equals(Telephony.Sms.MESSAGE_TYPE_FAILED)) {
Log.v("Deleting sms", "content://sms/" + id);
context.getContentResolver().delete(Uri.parse("content://sms/" + id), null, null);
}
}
if (msgBox != null && id != null) {
if (msgBox.equals(Telephony.Mms.MESSAGE_BOX_OUTBOX) || msgBox.equals(Telephony.Mms.MESSAGE_BOX_FAILED)) {
Log.v("Deleting mms", "content://mms/" + id);
context.getContentResolver().delete(Uri.parse("content://mms/" + id), null, null);
}
}
} while (myCursor.moveToNext());
}
} catch (SQLiteException e) {
String[] unfilteredColumns = {};
try (Cursor unfilteredColumnsCursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (unfilteredColumnsCursor != null) {
unfilteredColumns = unfilteredColumnsCursor.getColumnNames();
}
}
if (unfilteredColumns.length == 0) {
throw new MessageAccessException(uri, e);
} else {
throw new MessageAccessException(unfilteredColumns, uri, e);
}
}
}
/** /**
* Gets messages which match the selection * Gets messages which match the selection
* *
@ -818,6 +904,29 @@ public class SMSHelper {
} }
} }
/**
* converts a given JSONArray into List<Address>
*/
public static List<Address> jsonArrayToAddressList(JSONArray jsonArray) {
if (jsonArray == null) {
return null;
}
List<Address> addresses = new ArrayList<>();
try {
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String address = jsonObject.getString("address");
addresses.add(new Address(address));
Log.e("address", address);
}
} catch (Exception e) {
e.printStackTrace();
}
return addresses;
}
/** /**
* Indicate that some error has occurred while reading a message. * Indicate that some error has occurred while reading a message.
* More useful for logging than catching and handling * More useful for logging than catching and handling

View File

@ -0,0 +1,38 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.Plugins.SMSPlugin;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* A small BroadcastReceiver wrapper for MMSReceivedReceiver to load user preferences
*/
public class DelegatingMmsReceivedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
MmsReceivedReceiver delegate = new MmsReceivedReceiver();
delegate.loadFromPreferences(context);
delegate.onReceive(context, intent);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.Plugins.SMSPlugin;
import android.content.Intent;
import android.app.Service;
import android.os.IBinder;
/**
* Service for sending messages to a conversation without a UI present. These messages could come
* from something like Phone, needed to make default sms app
*/
public class HeadlessSmsSendService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.Plugins.SMSPlugin;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log;
import com.klinker.android.send_message.Transaction;
import org.kde.kdeconnect_tp.R;
/**
* Receiver for notifying user when a new MMS has been received by the device. By default it will
* persist the message to the internal database and notification service to notify the users will be
* implemented later.
*/
public class MmsReceivedReceiver extends com.klinker.android.send_message.MmsReceivedReceiver {
private String mmscUrl = null;
private String mmsProxy = null;
private String mmsPort = null;
@Override
public void onMessageReceived(Context context, Uri messageUri) {
Log.v("MmsReceived", "message received: " + messageUri.toString());
// Notify messageUpdateReceiver about the arrival of the new MMS message
Intent refreshIntent = new Intent(Transaction.REFRESH);
context.sendBroadcast(refreshIntent);
}
@Override
public void onError(Context context, String error) {
Log.v("MmsReceived", "error: " + error);
}
public void loadFromPreferences(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
mmscUrl = prefs.getString(context.getString(R.string.sms_pref_set_mmsc), "");
mmsProxy = prefs.getString(context.getString(R.string.sms_pref_set_mms_proxy), "");
mmsPort = prefs.getString(context.getString(R.string.sms_pref_set_mms_port), "");
}
/**
* some carriers will download duplicate MMS messages without this ACK. When using the
* system sending method, apparently Android does not do this for us. Not sure why.
* We might have to have users manually enter their APN settings if we cannot get them
* from the system somehow.
*/
@Override
public MmscInformation getMmscInfoForReceptionAck() {
if (mmscUrl != null || mmsProxy != null || mmsPort != null) {
try {
return new MmscInformation(mmscUrl, mmsProxy, Integer.parseInt(mmsPort));
} catch (Exception e) {
Log.e("MmsReceivedReceiver", "Exception", e);
return null;
}
} else {
return null;
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.Plugins.SMSPlugin;
import android.content.Context;
import android.content.Intent;
import com.klinker.android.send_message.Transaction;
import com.klinker.android.send_message.Utils;
public class MmsSentReceiver extends com.klinker.android.send_message.MmsSentReceiver {
@Override
public void updateInInternalDatabase(Context context, Intent intent, int resultCode) {
super.updateInInternalDatabase(context, intent, resultCode);
if (Utils.isDefaultSmsApp(context)) {
// Notify messageUpdateReceiver about the successful sending of the mms message
Intent refreshIntent = new Intent(Transaction.REFRESH);
context.sendBroadcast(refreshIntent);
}
}
@Override
public void onMessageStatusUpdated(Context context, Intent intent, int resultCode) {
}
}

View File

@ -39,7 +39,6 @@ import android.provider.Telephony;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager; import android.telephony.SmsManager;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import android.util.Log;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -63,6 +62,11 @@ import java.util.concurrent.locks.ReentrantLock;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.klinker.android.send_message.ApnUtils;
import com.klinker.android.send_message.Transaction;
import com.klinker.android.send_message.Utils;
import com.klinker.android.logger.Log;
import static org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin.PACKET_TYPE_TELEPHONY; import static org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin.PACKET_TYPE_TELEPHONY;
@PluginFactory.LoadablePlugin @PluginFactory.LoadablePlugin
@ -125,9 +129,10 @@ public class SMSPlugin extends Plugin {
* <p> * <p>
* The body should look like so: * The body should look like so:
* { "sendSms": true, * { "sendSms": true,
* "phoneNumber": "542904563213", * "phoneNumber": "542904563213" // For older desktop versions of SMS app this packet carries phoneNumber field
* "messageBody": "Hi mom!", * "addresses": <List of Addresses> // For newer desktop versions of SMS app it contains addresses field instead of phoneNumber field
* "sub_id": "3859358340534" * "messageBody": "Hi mom!",
* "sub_id": "3859358340534"
* } * }
*/ */
private final static String PACKET_TYPE_SMS_REQUEST = "kdeconnect.sms.request"; private final static String PACKET_TYPE_SMS_REQUEST = "kdeconnect.sms.request";
@ -210,33 +215,64 @@ public class SMSPlugin extends Plugin {
*/ */
@Override @Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
// Lock so no one uses the mostRecentTimestamp between the moment we read it and the // If the KDE Connect is set as default Sms app
// moment we update it. This is because reading the Messages DB can take long. // prevent from reading the latest message in the database before the sentReceivers mark it as sent
mostRecentTimestampLock.lock(); if (Utils.isDefaultSmsApp(context)) {
if (mostRecentTimestamp == 0) {
// Since the timestamp has not been initialized, we know that nobody else
// has requested a message. That being the case, there is most likely
// nobody listening for message updates, so just drop them
mostRecentTimestampLock.unlock();
return; return;
} }
SMSHelper.Message message = SMSHelper.getNewestMessage(context); sendLatestMessage();
if (message == null || message.date <= mostRecentTimestamp) {
// onChange can trigger many times for a single message. Don't make unnecessary noise
mostRecentTimestampLock.unlock();
return;
}
// Update the most recent counter
mostRecentTimestamp = message.date;
mostRecentTimestampLock.unlock();
// Send the alert about the update
device.sendPacket(constructBulkMessagePacket(Collections.singleton(message)));
} }
}
/**
* This receiver will be invoked only when the app will be set as the default sms app
* Whenever the app will be set as the default, the database update alert will be sent
* using messageUpdateReceiver and not the contentObserver class
*/
private final BroadcastReceiver messagesUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Transaction.REFRESH.equals(action)) {
sendLatestMessage();
}
}
};
/**
* Helper method to read the latest message from the sms-mms database and sends it to the desktop
*/
private void sendLatestMessage() {
// Lock so no one uses the mostRecentTimestamp between the moment we read it and the
// moment we update it. This is because reading the Messages DB can take long.
mostRecentTimestampLock.lock();
if (mostRecentTimestamp == 0) {
// Since the timestamp has not been initialized, we know that nobody else
// has requested a message. That being the case, there is most likely
// nobody listening for message updates, so just drop them
mostRecentTimestampLock.unlock();
return;
}
SMSHelper.Message message = SMSHelper.getNewestMessage(context);
if (message == null || message.date <= mostRecentTimestamp) {
// onChange can trigger many times for a single message. Don't make unnecessary noise
mostRecentTimestampLock.unlock();
return;
}
// Update the most recent counter
mostRecentTimestamp = message.date;
mostRecentTimestampLock.unlock();
// Send the alert about the update
device.sendPacket(constructBulkMessagePacket(Collections.singleton(message)));
Log.e("sent", "update");
} }
/** /**
@ -304,6 +340,10 @@ public class SMSPlugin extends Plugin {
filter.setPriority(500); filter.setPriority(500);
context.registerReceiver(receiver, filter); context.registerReceiver(receiver, filter);
IntentFilter refreshFilter = new IntentFilter(Transaction.REFRESH);
refreshFilter.setPriority(500);
context.registerReceiver(messagesUpdateReceiver, refreshFilter);
Looper helperLooper = SMSHelper.MessageLooper.getLooper(); Looper helperLooper = SMSHelper.MessageLooper.getLooper();
ContentObserver messageObserver = new MessageContentObserver(new Handler(helperLooper)); ContentObserver messageObserver = new MessageContentObserver(new Handler(helperLooper));
SMSHelper.registerObserver(messageObserver, context); SMSHelper.registerObserver(messageObserver, context);
@ -312,6 +352,13 @@ public class SMSPlugin extends Plugin {
Log.w("SMSPlugin", "This is a very old version of Android. The SMS Plugin might not function as intended."); Log.w("SMSPlugin", "This is a very old version of Android. The SMS Plugin might not function as intended.");
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
ApnUtils.initDefaultApns(context, null);
}
// To see debug messages for Klinker library, uncomment the below line
//Log.setDebug(true);
return true; return true;
} }
@ -334,8 +381,22 @@ public class SMSPlugin extends Plugin {
case PACKET_TYPE_SMS_REQUEST_CONVERSATION: case PACKET_TYPE_SMS_REQUEST_CONVERSATION:
return this.handleRequestConversation(np); return this.handleRequestConversation(np);
case PACKET_TYPE_SMS_REQUEST: case PACKET_TYPE_SMS_REQUEST:
// Fall through to old-style handling if (np.getBoolean("sendSms")) {
// This space may be filled in differently once MMS support is implemented String textMessage = np.getString("messageBody");
long subID = np.getLong("subID", -1);
List<SMSHelper.Address> addressList = SMSHelper.jsonArrayToAddressList(np.getJSONArray("addresses"));
if (addressList == null) {
// If the List of Address is null, then the SMS_REQUEST packet is
// most probably from the older version of the desktop app.
addressList = new ArrayList<>();
addressList.add(new SMSHelper.Address(np.getString("phoneNumber")));
}
SmsMmsUtils.sendMessage(context, textMessage, addressList, (int) subID);
}
break;
case TelephonyPlugin.PACKET_TYPE_TELEPHONY_REQUEST: case TelephonyPlugin.PACKET_TYPE_TELEPHONY_REQUEST:
if (np.getBoolean("sendSms")) { if (np.getBoolean("sendSms")) {
String phoneNo = np.getString("phoneNumber"); String phoneNo = np.getString("phoneNumber");
@ -432,6 +493,17 @@ public class SMSPlugin extends Plugin {
conversation = SMSHelper.getMessagesInRange(this.context, threadID, rangeStartTimestamp, numberToGet); conversation = SMSHelper.getMessagesInRange(this.context, threadID, rangeStartTimestamp, numberToGet);
} }
// Sometimes when desktop app is kept open while android app is restarted for any reason
// mostRecentTimeStamp must be updated in that scenario too if a user request for a
// single conversation and not the entire conversation list
mostRecentTimestampLock.lock();
for (SMSHelper.Message message : conversation) {
if (message.date > mostRecentTimestamp) {
mostRecentTimestamp = message.date;
}
}
mostRecentTimestampLock.unlock();
NetworkPacket reply = constructBulkMessagePacket(conversation); NetworkPacket reply = constructBulkMessagePacket(conversation);
device.sendPacket(reply); device.sendPacket(reply);
@ -451,6 +523,10 @@ public class SMSPlugin extends Plugin {
return false; return false;
} }
@Override
public boolean hasSettings() {
return true;
}
@Override @Override
public String[] getSupportedPacketTypes() { public String[] getSupportedPacketTypes() {
@ -478,6 +554,19 @@ public class SMSPlugin extends Plugin {
}; };
} }
/**
* Permissions required for sending and receiving MMs messages
*/
public static String[] getMmsPermissions() {
return new String[]{
Manifest.permission.RECEIVE_SMS,
Manifest.permission.RECEIVE_MMS,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CHANGE_NETWORK_STATE,
Manifest.permission.WAKE_LOCK,
};
}
/** /**
* With versions older than KITKAT, lots of the content providers used in SMSHelper become * With versions older than KITKAT, lots of the content providers used in SMSHelper become
* un-documented. Most manufacturers *did* do things the same way as was done in mainline * un-documented. Most manufacturers *did* do things the same way as was done in mainline

View File

@ -0,0 +1,121 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.Plugins.SMSPlugin;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import com.klinker.android.send_message.Message;
import com.klinker.android.send_message.MmsSentReceiver;
import com.klinker.android.send_message.Settings;
import com.klinker.android.send_message.Transaction;
import com.klinker.android.send_message.Utils;
import org.kde.kdeconnect.Helpers.SMSHelper;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.List;
public class SmsMmsUtils {
private static final String SENDING_MESSAGE = "Sending message";
/**
* Sends SMS or MMS message.
*
* @param context context in which the method is called.
* @param textMessage text body of the message to be sent.
* @param addressList List of addresses.
* @param subID Note that here subID is of type int and not long because klinker library requires it as int
* I don't really know the exact reason why they implemented it as int instead of long
*/
public static void sendMessage(Context context, String textMessage, List<SMSHelper.Address> addressList, int subID) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean longTextAsMms = prefs.getBoolean(context.getString(R.string.set_long_text_as_mms), false);
boolean groupMessageAsMms = prefs.getBoolean(context.getString(R.string.set_group_message_as_mms), true);
int sendLongAsMmsAfter = Integer.parseInt(
prefs.getString(context.getString(R.string.convert_to_mms_after),
context.getString(R.string.convert_to_mms_after_default)));
try {
Settings settings = new Settings();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// If the build version is less than lollipop then we have to manually take the APN settings
// from the user in order to be able to send MMS.
settings.setMmsc(prefs.getString(context.getString(R.string.sms_pref_set_mmsc), ""));
settings.setProxy(prefs.getString(context.getString(R.string.sms_pref_set_mms_proxy), ""));
settings.setPort(prefs.getString(context.getString(R.string.sms_pref_set_mms_port), ""));
}
settings.setUseSystemSending(true);
if (Utils.isDefaultSmsApp(context)) {
settings.setSendLongAsMms(longTextAsMms);
settings.setSendLongAsMmsAfter(sendLongAsMmsAfter);
}
settings.setGroup(groupMessageAsMms);
if (subID != -1) {
settings.setSubscriptionId(subID);
}
Transaction transaction = new Transaction(context, settings);
transaction.setExplicitBroadcastForSentSms(new Intent(context, SmsSentReceiver.class));
transaction.setExplicitBroadcastForSentMms(new Intent(context, MmsSentReceiver.class));
List<String> addresses = new ArrayList<>();
for (SMSHelper.Address address : addressList) {
addresses.add(address.toString());
}
Message message = new Message(textMessage, addresses.toArray(new String[0]));
message.setSave(true);
// Sending MMS on android requires the app to be set as the default SMS app,
// but sending SMS doesn't needs the app to be set as the default app.
// This is the reason why there are separate branch handling for SMS and MMS.
if (transaction.checkMMS(message)) {
if (Utils.isDefaultSmsApp(context)) {
if (Utils.isMobileDataEnabled(context)) {
com.klinker.android.logger.Log.v("", "Sending new MMS");
transaction.sendNewMessage(message, Transaction.NO_THREAD_ID);
}
} else {
com.klinker.android.logger.Log.v(SENDING_MESSAGE, "KDE Connect is not set to default SMS app.");
//TODO: Notify other end that they need to enable the mobile data in order to send MMS
}
} else {
com.klinker.android.logger.Log.v(SENDING_MESSAGE, "Sending new SMS");
transaction.sendNewMessage(message, Transaction.NO_THREAD_ID);
}
//TODO: Notify other end
} catch (Exception e) {
//TODO: Notify other end
com.klinker.android.logger.Log.e(SENDING_MESSAGE, "Exception", e);
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.Plugins.SMSPlugin;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.provider.Telephony.Sms;
import android.net.Uri;
import android.content.ContentValues;
import com.klinker.android.send_message.Transaction;
import com.klinker.android.send_message.Utils;
public class SmsReceiver extends BroadcastReceiver {
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
@Override
public void onReceive(Context context, Intent intent) {
if (!Utils.isDefaultSmsApp(context)) {
return;
}
if (intent != null && intent.getAction().equals(SMS_RECEIVED)) {
Bundle dataBundle = intent.getExtras();
if (dataBundle != null) {
Object[] smsExtra = (Object[]) dataBundle.get("pdus");
final SmsMessage[] message = new SmsMessage[smsExtra.length];
for (int i = 0; i < smsExtra.length; ++i) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = dataBundle.getString("format");
message[i] = SmsMessage.createFromPdu((byte[]) smsExtra[i], format);
} else {
message[i] = SmsMessage.createFromPdu((byte[]) smsExtra[i]);
}
// Write the received sms to the sms provider
for (SmsMessage msg : message) {
ContentValues values = new ContentValues();
values.put(Sms.ADDRESS, msg.getDisplayOriginatingAddress());
values.put(Sms.BODY, msg.getMessageBody());
values.put(Sms.DATE, System.currentTimeMillis()+"");
values.put(Sms.TYPE, Sms.MESSAGE_TYPE_INBOX);
values.put(Sms.STATUS, msg.getStatus());
values.put(Sms.READ, 0);
values.put(Sms.SEEN, 0);
context.getApplicationContext().getContentResolver().insert(Uri.parse("content://sms/"), values);
// Notify messageUpdateReceiver about the arrival of the new sms message
Intent refreshIntent = new Intent(Transaction.REFRESH);
context.sendBroadcast(refreshIntent);
}
}
}
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.Plugins.SMSPlugin;
import android.content.Context;
import android.content.Intent;
import com.klinker.android.send_message.SentReceiver;
import com.klinker.android.send_message.Transaction;
import com.klinker.android.send_message.Utils;
public class SmsSentReceiver extends SentReceiver {
@Override
public void updateInInternalDatabase(Context context, Intent intent, int receiverResultCode) {
super.updateInInternalDatabase(context, intent, receiverResultCode);
if (Utils.isDefaultSmsApp(context)) {
// Notify messageUpdateReceiver about the successful sending of the sms message
Intent refreshIntent = new Intent(Transaction.REFRESH);
context.sendBroadcast(refreshIntent);
}
}
@Override
public void onMessageStatusUpdated(Context context, Intent intent, int receiverResultCode) {
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2019 Erik Duisters <e.duisters1@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/>.
*/
package org.kde.kdeconnect.UserInterface;
import android.app.Activity;
import android.app.role.RoleManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Telephony;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
public class DefaultSmsAppAlertDialogFragment extends AlertDialogFragment {
private static final String KEY_PERMISSIONS = "Permissions";
private static final String KEY_REQUEST_CODE = "RequestCode";
private String[] permissions;
private int requestCode;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args == null) {
return;
}
permissions = args.getStringArray(KEY_PERMISSIONS);
requestCode = args.getInt(KEY_REQUEST_CODE, 0);
setCallback(new Callback() {
@Override
public void onPositiveButtonClicked() {
Activity host = requireActivity();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
RoleManager roleManager = host.getSystemService(RoleManager.class);
if (roleManager.isRoleAvailable(RoleManager.ROLE_SMS)) {
if (!roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
Intent roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS);
host.startActivityForResult(roleRequestIntent, requestCode);
}
}
} else {
Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, getActivity().getPackageName());
host.startActivityForResult(intent, requestCode);
}
ActivityCompat.requestPermissions(requireActivity(), permissions, requestCode);
}
});
}
public static class Builder extends AlertDialogFragment.AbstractBuilder<DefaultSmsAppAlertDialogFragment.Builder, DefaultSmsAppAlertDialogFragment> {
@Override
public Builder getThis() {
return this;
}
public Builder setPermissions(String[] permissions) {
args.putStringArray(KEY_PERMISSIONS, permissions);
return getThis();
}
public Builder setRequestCode(int requestCode) {
args.putInt(KEY_REQUEST_CODE, requestCode);
return getThis();
}
@Override
protected DefaultSmsAppAlertDialogFragment createFragment() {
return new DefaultSmsAppAlertDialogFragment();
}
}
}

View File

@ -40,14 +40,18 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.klinker.android.send_message.Utils;
import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.SMSPlugin.SMSPlugin;
import org.kde.kdeconnect.UserInterface.List.PluginListHeaderItem; import org.kde.kdeconnect.UserInterface.List.PluginListHeaderItem;
import org.kde.kdeconnect.UserInterface.List.FailedPluginListItem; import org.kde.kdeconnect.UserInterface.List.FailedPluginListItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.PluginItem; import org.kde.kdeconnect.UserInterface.List.PluginItem;
import org.kde.kdeconnect.UserInterface.List.SetDefaultAppPluginListItem;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
import java.util.ArrayList; import java.util.ArrayList;
@ -337,6 +341,29 @@ public class DeviceFragment extends Fragment {
dialog.show(getChildFragmentManager(), null); dialog.show(getChildFragmentManager(), null);
} }
}); });
// Add a button to the pluginList for setting KDE Connect as default sms app for allowing it to send mms
// for now I'm not able to integrate it with other plugin list, but this needs to be reimplemented in a better way.
if (!Utils.isDefaultSmsApp(mActivity)) {
for (Plugin p : plugins) {
if (p.getPluginKey().equals("SMSPlugin")) {
pluginListItems.add(new SetDefaultAppPluginListItem(p, mActivity.getResources().getString(R.string.pref_plugin_telepathy_mms), (action) -> {
DialogFragment dialog = new DefaultSmsAppAlertDialogFragment.Builder()
.setTitle(R.string.set_default_sms_app_title)
.setMessage(R.string.pref_plugin_telepathy_mms_desc)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.setPermissions(SMSPlugin.getMmsPermissions())
.setRequestCode(MainActivity.RESULT_NEEDS_RELOAD)
.create();
if (dialog != null) {
dialog.show(getChildFragmentManager(), null);
}
}));
}
}
}
} }
ListAdapter adapter = new ListAdapter(mActivity, pluginListItems); ListAdapter adapter = new ListAdapter(mActivity, pluginListItems);

View File

@ -0,0 +1,33 @@
/*
* Copyright 2020 Aniket Kumar <anikketkumar786@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/>.
*/
package org.kde.kdeconnect.UserInterface.List;
import org.kde.kdeconnect.Plugins.Plugin;
public class SetDefaultAppPluginListItem extends SmallEntryItem {
public interface Action {
void action(Plugin plugin);
}
public SetDefaultAppPluginListItem(Plugin plugin, String displayName, SetDefaultAppPluginListItem.Action action) {
super(displayName, (view) -> action.action(plugin));
}
}