Android- 以编程方式发送彩信而不是默认应用程序

Android- Sending MMS Programmatically Without Being Default App

Android SDK 的 MMS 部分基本上没有公开,因此确实 one main 3rd party library that helps out with this. The sample project included in the library will not send MMS, however, a fork of the project 公开了。

当我下载后一个项目时,我可以运行示例并发送彩信,而无需将应用程序设置为默认应用程序。但是,当我将代码集成到我自己的应用程序中时,只有当我将我的应用程序设为默认消息应用程序时,彩信才会发送。我读过 Android 4.4+ 不支持彩信,但不是默认应用程序,但为什么我从中派生代码的示例项目没有默认?我真正注意到的唯一区别是最小 SDK 是 14 而我的项目是 18。

这是我项目的主要代码:

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hacknow2">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SEND_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.permission.VIBRATE" />
    <uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>

    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <application
        android:name=".init.App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".phase1.PermissionsActivity"/>

        <activity android:name=".phase2.youtube.YoutubeActivity" />

        <activity android:name=".phase2.posting.PostingActivity" />
        <activity
            android:name=".phase2.ui.main.SelectionActivity"
            android:label="@string/title_activity_selection"
            android:theme="@style/AppTheme.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>
        </activity>
        <activity android:name=".phase1.OpeningActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver
            android:name=".phase3.mms.KitSmsSentReceiver"
            android:permission="android.permission.BROADCAST_SMS">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_DELIVER" />
            </intent-filter>
        </receiver>

        <receiver
            android:name=".phase3.mms.KitMmsSentReceiver"
            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="com.klinker.android.send_message.MmsSentReceiver"
            android:taskAffinity="com.klinker.android.messaging.MMS_SENT" />

        <receiver
            android:name="com.klinker.android.send_message.MmsReceivedReceiver"
            android:taskAffinity="com.klinker.android.messaging.MMS_RECEIVED" />

        <service android:name="com.android.mms.transaction.TransactionService" />

        <service
            android:name=".phase3.mms.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>
    </application>

</manifest>

MMSManager,管理发送彩信的单例

public class MMSManager {

    private static final String TAG = "MMSManager";
    private static ThreadPoolExecutor mThreadManager;
    private static MMSManager INSTANCE;
    private static BlockingQueue<Runnable> decodeWorkQueue;
    private static int NUMBER_OF_CORES =
            Runtime.getRuntime().availableProcessors();
    private Settings mSettings;
    private Context mContext;

    private MMSManager(Context c) {
        mContext = c;
        initSettings();
        initLogging();
        // A queue of Runnables
        decodeWorkQueue = new LinkedBlockingQueue<Runnable>();
        // setting the thread factory
        mThreadManager = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES,
                50, TimeUnit.MILLISECONDS, decodeWorkQueue);

        BroadcastUtils.sendExplicitBroadcast(c, new Intent(), "test action");
    }

    //See 
    private static synchronized MMSManager getSync() {
        if (INSTANCE == null) INSTANCE = new MMSManager(App.get());
        return INSTANCE;
    }

    public static MMSManager getInstance(Context c) {
        if (INSTANCE == null) {
            INSTANCE = getSync();
        }
        return INSTANCE;
    }

    private void initSettings() {
        mSettings = Settings.get(mContext);

        if (TextUtils.isEmpty(mSettings.getMmsc()) &&
                Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            initApns();
        }
    }

    private void initApns() {
        ApnUtils.initDefaultApns(mContext, new ApnUtils.OnApnFinishedListener() {
            @Override
            public void onFinished() {
                mSettings = Settings.get(mContext, true);
            }
        });
    }

    private void initLogging() {
        com.klinker.android.logger.Log.setDebug(true);
        com.klinker.android.logger.Log.setPath("messenger_log.txt");
        com.klinker.android.logger.Log.setLogListener(new OnLogListener() {
            @Override
            public void onLogged(String tag, String message) {
                //logAdapter.addItem(tag + ": " + message);
                android.util.Log.d("MMS_Manager " + tag, "onLogged: " + message);
            }
        });
    }

    //Not sure what exception might pop up but it's being handled anyway...
    public void sendMMS(String phoneNumber, Bitmap bm) {
        mThreadManager.execute(new Runnable() {
            @Override
            public void run() {
                Log.d("ThreadPool/MMSManager", "Trying to send MMS.");
                com.klinker.android.send_message.Settings sendSettings = new com.klinker.android.send_message.Settings();
                sendSettings.setMmsc(mSettings.getMmsc());
                sendSettings.setProxy(mSettings.getMmsProxy());
                sendSettings.setPort(mSettings.getMmsPort());
                sendSettings.setUseSystemSending(true);

                Transaction transaction = new Transaction(mContext, sendSettings);

                Message message = new Message(null, phoneNumber);

                if (bm != null)
                    message.setImage(bm);

                transaction.sendNewMessage(message, Transaction.NO_THREAD_ID);
            }
        });
    }
}

调用 MMSManager 方法的片段,位于其父 SelectionActivity 中:

public class SendDialog extends DialogFragment {

    private static final int MY_PERMISSIONS_REQUEST_SEND_SMS = 1000;
    private ArrayList<String> mPhoneNumbers;
    private DMessage mMessage;
    private OnFinished mCompletionListener;
    private MMSManager mMMSManager;
    private Bitmap mBitmap;
    private static String[] mMediaTypes;

    public SendDialog(ArrayList<String> phoneNumbers, DMessage m, OnFinished listener){
        mPhoneNumbers = phoneNumbers;
        mMessage = m;
        mCompletionListener = listener;
    }

    public SendDialog(ArrayList<String> phoneNumbers, DMessage m, Bitmap b, OnFinished listener){
        mPhoneNumbers = phoneNumbers;
        mMessage = m;
        mCompletionListener = listener;
        mBitmap = b;
    }

    public interface OnFinished{
        void complete(boolean success);
    }

    private String getCautionMessage(){
        int numContacts = 0;
        if(mPhoneNumbers!=null && mPhoneNumbers.size()>0) {
            for (String number :
                    mPhoneNumbers) {
                numContacts++;
            }
        }
        return "Are you sure you'd like to send the highlighted post to " + numContacts + " people?";
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        mMediaTypes = getResources().getStringArray(R.array.postoptions);
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(getCautionMessage())
                .setPositiveButton(R.string.OK, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        sendBulkTexts();
                        dismiss();
                    }
                })
                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        mCompletionListener.complete(false);
                    }
                });
        // Create the AlertDialog object and return it
        return builder.create();
    }

    private boolean isMMS(){
        return mMessage.getMediaType().equals(mMediaTypes[2]);//mediaTypes[2] == "Image"
    }

    /**
     * Converts phone number to only numbers.
     * I.e. (XXX)-XXX-XXXX to XXXXXXXXXX
     */
    private static String phoneNumberFormatter(String unformatted) {
        return unformatted.replaceAll("\D+", "");
    }

    private void sendBulkTexts(){

        for (String number :
                mPhoneNumbers) {
            try {
                String formattedNumber = phoneNumberFormatter(number);
                if(!isMMS()) {
                    //Send a Youtube video or text
                    sendSMS(formattedNumber, mMessage.getBody());
                }
                else{
                    if(mMMSManager==null)//instantiate MMSManager and load bitmap
                        mMMSManager = MMSManager.getInstance(getActivity());
                    if(mBitmap!=null)
                        mMMSManager.sendMMS(formattedNumber, mBitmap);
                }
            }
            catch(Exception e){
                Toast.makeText(getActivity(), "Could not send text to " + number + ".", Toast.LENGTH_LONG).show();
                Log.e("SendDialog", "sendBulkTexts: ", e);
                mCompletionListener.complete(false);
            }
        }
        Toast.makeText(getActivity(), "Finished sending texts.", Toast.LENGTH_LONG).show();
        mCompletionListener.complete(true);
    }

    private void sendSMS(String phoneNumber, String message) throws Exception{
        SmsManager smsManager = SmsManager.getDefault();
        smsManager.sendTextMessage(phoneNumber, null, message, null, null);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_SEND_SMS: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    sendBulkTexts();
                } else {
                    Toast.makeText(getActivity(),
                            "Permission for sending SMSs denied.", Toast.LENGTH_LONG).show();
                    mCompletionListener.complete(false);
                    return;
                }
            }
        }
    }
}

我在声明消息后添加了以下几行,它似乎有效。

message.setFromAddress(Utils.getMyPhoneNumber(mContext));
message.setSave(false);
Uri uri = Uri.parse("content://mms-sms/conversations/");
message.setMessageUri(uri);

我不确定叉子在做什么,但我怀疑它没有使用系统消息传递库,而是使用 Klinker 库发送消息。这就是允许它在不将应用设置为默认应用的情况下发送消息的原因。

要使其在没有系统库的情况下工作,您需要将 Save 设置为 false。然后你需要提供一个 URI 让它工作。 FromAddress 可能需要也可能不需要。

我找到了答案。

显然,在 2019 年 Google 更改了他们的规则,因此您不能将应用添加到 Google 直接从您的应用发送 SMS/MMS 的 Play 商店 if没有某种强有力的理由,它不是默认的消息传递应用程序。您可以做的就是使用您的短信和彩信创建一个意图并将其传递给 Android OS 以使用您的默认消息传递应用程序。

我的建议是避免尝试使用本机 API 编写 SMS/MMS(除非您绝对确信自己是 Google Play 条款的罕见例外)并使用 Twilio 等第三方服务为您处理。