Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5c3b6658 authored by David Braun's avatar David Braun
Browse files

Implement new method for handling SMS/MMS on the platform

Multi project change:
The changes in this project implement the actual change in the SMS related
intent behavior (defining and using the new events).

Bug: 10449618
Change-Id: Ia707ed561d89428db78203a2ed54504d867a3e43
parent 7edea181
Loading
Loading
Loading
Loading
+97 −5
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.provider;

import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -30,6 +31,8 @@ import android.text.TextUtils;
import android.telephony.Rlog;
import android.util.Patterns;

import com.android.internal.telephony.SmsApplication;


import java.util.HashSet;
import java.util.Set;
@@ -519,17 +522,73 @@ public final class Telephony {
            public static final int RESULT_SMS_UNSUPPORTED = 4;

            /**
             * Set by BroadcastReceiver. Indicates the duplicated imcoming message.
             * Set by BroadcastReceiver. Indicates the duplicated incoming message.
             */
            public static final int RESULT_SMS_DUPLICATED = 5;

            /**
             * Activity action: Ask the user to change the default
             * SMS application. This will show a dialog that asks the
             * user whether they want to replace the current default
             * SMS application with the one specified in
             * {@link #EXTRA_PACKAGE_NAME}.
             */
            @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
            public static final String ACTION_CHANGE_DEFAULT =
                    "android.provider.Telephony.ACTION_CHANGE_DEFAULT";

            /**
             * The PackageName string passed in as an
             * extra for {@link #ACTION_CHANGE_DEFAULT}
             *
             * @see #ACTION_CHANGE_DEFAULT
             */
            public static final String EXTRA_PACKAGE_NAME = "package";

            /**
             * Used by applications to determine if they are the current default sms package.
             * @param context context of the requesting application
             * @param packageName name of the package to check.
             * @return true if the specified package is the current default SMS app.
             */
            public static boolean isDefaultSmsPackage(Context context, String packageName) {
                ComponentName component = SmsApplication.getDefaultSmsApplication(context, false);
                if (component != null && component.getPackageName().equals(packageName)) {
                    return true;
                }
                return false;
            }

            /**
             * Broadcast Action: A new text based SMS message has been received
             * by the device. The intent will have the following extra
             * by the device. This intent will only be delivered to the default
             * sms app. That app is responsible for writing the message and notifying
             * the user. The intent will have the following extra values:</p>
             *
             * <ul>
             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
             *   that make up the message.</li>
             * </ul>
             *
             * <p>The extra values can be extracted using
             * {@link #getMessagesFromIntent(Intent)}.</p>
             *
             * <p>If a BroadcastReceiver encounters an error while processing
             * this intent it should set the result code appropriately.</p>
             */
            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
            public static final String SMS_DELIVER_ACTION =
                    "android.provider.Telephony.SMS_DELIVER";

            /**
             * Broadcast Action: A new text based SMS message has been received
             * by the device. This intent will be delivered to all registered
             * receivers as a notification. These apps are not expected to write the
             * message or notify the user. The intent will have the following extra
             * values:</p>
             *
             * <ul>
             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
             *   that make up the message.</li>
             * </ul>
             *
@@ -545,7 +604,8 @@ public final class Telephony {

            /**
             * Broadcast Action: A new data based SMS message has been received
             * by the device. The intent will have the following extra
             * by the device. This intent will be delivered to all registered
             * receivers as a notification. The intent will have the following extra
             * values:</p>
             *
             * <ul>
@@ -565,7 +625,39 @@ public final class Telephony {

            /**
             * Broadcast Action: A new WAP PUSH message has been received by the
             * device. The intent will have the following extra
             * device. This intent will only be delivered to the default
             * sms app. That app is responsible for writing the message and notifying
             * the user. The intent will have the following extra values:</p>
             *
             * <ul>
             *   <li><em>transactionId (Integer)</em> - The WAP transaction ID</li>
             *   <li><em>pduType (Integer)</em> - The WAP PDU type</li>
             *   <li><em>header (byte[])</em> - The header of the message</li>
             *   <li><em>data (byte[])</em> - The data payload of the message</li>
             *   <li><em>contentTypeParameters (HashMap&lt;String,String&gt;)</em>
             *   - Any parameters associated with the content type
             *   (decoded from the WSP Content-Type header)</li>
             * </ul>
             *
             * <p>If a BroadcastReceiver encounters an error while processing
             * this intent it should set the result code appropriately.</p>
             *
             * <p>The contentTypeParameters extra value is map of content parameters keyed by
             * their names.</p>
             *
             * <p>If any unassigned well-known parameters are encountered, the key of the map will
             * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter.  If
             * a parameter has No-Value the value in the map will be null.</p>
             */
            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
            public static final String WAP_PUSH_DELIVER_ACTION =
                    "android.provider.Telephony.WAP_PUSH_DELIVER";

            /**
             * Broadcast Action: A new WAP PUSH message has been received by the
             * device. This intent will be delivered to all registered
             * receivers as a notification. These apps are not expected to write the
             * message or notify the user. The intent will have the following extra
             * values:</p>
             *
             * <ul>
+47 −19
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.internal.telephony;
import android.app.Activity;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -627,11 +628,22 @@ public abstract class InboundSmsHandler extends StateMachine {

        Intent intent;
        if (destPort == -1) {
            intent = new Intent(Intents.SMS_RECEIVED_ACTION);
            intent = new Intent(Intents.SMS_DELIVER_ACTION);

            // Direct the intent to only the default SMS app. If we can't find a default SMS app
            // then sent it to all broadcast receivers.
            ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true);
            if (componentName != null) {
                // Deliver SMS message only to this receiver
                intent.setComponent(componentName);
                log("Delivering SMS to: " + componentName.getPackageName() +
                        " " + componentName.getClassName());
            }
        } else {
            Uri uri = Uri.parse("sms://localhost:" + destPort);
            intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
        }

        intent.putExtra("pdus", pdus);
        intent.putExtra("format", tracker.getFormat());
        dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
@@ -769,8 +781,23 @@ public abstract class InboundSmsHandler extends StateMachine {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (!Intents.SMS_RECEIVED_ACTION.equals(action)
                    && !Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) {
            if (action.equals(Intents.SMS_DELIVER_ACTION)) {
                // Now dispatch the notification only intent
                intent.setAction(Intents.SMS_RECEIVED_ACTION);
                intent.setComponent(null);
                dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
                        AppOpsManager.OP_RECEIVE_SMS, this);
            } else if (action.equals(Intents.WAP_PUSH_DELIVER_ACTION)) {
                // Now dispatch the notification only intent
                intent.setAction(Intents.WAP_PUSH_RECEIVED_ACTION);
                intent.setComponent(null);
                dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
                        AppOpsManager.OP_RECEIVE_SMS, this);
            } else {
                // Now that the intents have been deleted we can clean up the PDU data.
                if (!Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
                        && !Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
                        && !Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
                    loge("unexpected BroadcastReceiver action: " + action);
                }

@@ -793,6 +820,7 @@ public abstract class InboundSmsHandler extends StateMachine {
                }
            }
        }
    }

    /**
     * Log with debug level.
+258 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony;

import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.Settings;
import android.provider.Telephony.Sms.Intents;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * Class for managing the primary application that we will deliver SMS/MMS messages to
 *
 * {@hide}
 */
public final class SmsApplication {
    private static final String MESSAGING_PACKAGE_NAME = "com.android.mms";

    public static class SmsApplicationData {
        /**
         * Name of this SMS app for display.
         */
        public String mApplicationName;

        /**
         * Package name for this SMS app.
         */
        public String mPackageName;

        /**
         * The class name of the SMS receiver in this app.
         */
        public String mSmsReceiverClass;

        /**
         * The class name of the MMS receiver in this app.
         */
        public String mMmsReceiverClass;

        /**
         * The user-id for this application
         */
        public int mUid;

        public SmsApplicationData(String applicationName, String packageName,
                String smsReceiverName, String mmsReceiverName, int uid) {
            mApplicationName = applicationName;
            mPackageName = packageName;
            mSmsReceiverClass = smsReceiverName;
            mMmsReceiverClass = mmsReceiverName;
            mUid = uid;
        }
    }

    /**
     * Returns the list of available SMS apps defined as apps that are registered for both the
     * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
     * receivers are enabled)
     */
    public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
        PackageManager packageManager = context.getPackageManager();

        // Get the list of apps registered for SMS
        Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
        int flags = 0;
        List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceivers(intent, flags);

        intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
        intent.setDataAndType(null, "application/vnd.wap.mms-message");
        List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceivers(intent, flags);

        HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();

        // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers)
        for (ResolveInfo r : smsReceivers) {
            String packageName = r.activityInfo.packageName;
            if (!receivers.containsKey(packageName)) {
                String applicationName = r.loadLabel(packageManager).toString();
                SmsApplicationData smsApplicationData = new SmsApplicationData(applicationName,
                        packageName, r.activityInfo.name, null, r.activityInfo.applicationInfo.uid);
                receivers.put(packageName, smsApplicationData);
            }
        }

        // Update any existing entries with mms receiver class
        for (ResolveInfo r : mmsReceivers) {
            String packageName = r.activityInfo.packageName;
            SmsApplicationData smsApplicationData = receivers.get(packageName);
            if (smsApplicationData != null) {
                smsApplicationData.mMmsReceiverClass = r.activityInfo.name;
            }
        }

        // Remove any entries (which we added for sms receivers) for which we did not also find
        // valid mms receivers
        for (ResolveInfo r : smsReceivers) {
            String packageName = r.activityInfo.packageName;
            SmsApplicationData smsApplicationData = receivers.get(packageName);
            if (smsApplicationData != null && smsApplicationData.mMmsReceiverClass == null) {
                receivers.remove(packageName);
            }
        }
        return receivers.values();
    }

    /**
     * Checks to see if we have a valid installed SMS application for the specified package name
     * @return Data for the specified package name or null if there isn't one
     */
    private static SmsApplicationData getApplicationForPackage(
            Collection<SmsApplicationData> applications, String packageName) {
        if (packageName == null) {
            return null;
        }
        // Is there an entry in the application list for the specified package?
        for (SmsApplicationData application : applications) {
            if (application.mPackageName.contentEquals(packageName)) {
                return application;
            }
        }
        return null;
    }

    /**
     * Get the application we will use for delivering SMS/MMS messages.
     *
     * We return the preferred sms application with the following order of preference:
     * (1) User selected SMS app (if selected, and if still valid)
     * (2) Android Messaging (if installed)
     * (3) The currently configured highest priority broadcast receiver
     * (4) Null
     */
    private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded) {
        Collection<SmsApplicationData> applications = getApplicationCollection(context);

        // Determine which application receives the broadcast
        String defaultApplication = Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.SMS_DEFAULT_APPLICATION);

        SmsApplicationData applicationData = null;
        if (defaultApplication != null) {
            applicationData = getApplicationForPackage(applications, defaultApplication);
        }
        // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
        // this if the caller asked us to.
        if (updateIfNeeded) {
            if (applicationData == null) {
                // Do we have a package for Android Messaging?
                applicationData = getApplicationForPackage(applications, MESSAGING_PACKAGE_NAME);
            }
            if (applicationData == null) {
                // Are there any applications?
                if (applications.size() != 0) {
                    applicationData = (SmsApplicationData)applications.toArray()[0];
                }
            }

            // If we found a new default app, update the setting
            if (applicationData != null) {
                setDefaultApplication(applicationData.mPackageName, context);
            }
        }
        return applicationData;
    }

    /**
     * Sets the specified package as the default SMS/MMS application. The caller of this method
     * needs to have permission to set AppOps and write to secure settings.
     */
    public static void setDefaultApplication(String packageName, Context context) {
        Collection<SmsApplicationData> applications = getApplicationCollection(context);
        String oldPackageName = Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.SMS_DEFAULT_APPLICATION);
        SmsApplicationData oldSmsApplicationData = getApplicationForPackage(applications,
                oldPackageName);
        SmsApplicationData smsApplicationData = getApplicationForPackage(applications,
                packageName);

        if (smsApplicationData != null && smsApplicationData != oldSmsApplicationData) {
            // Ignore OP_WRITE_SMS for the previously configured default SMS app.
            AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
            if (oldSmsApplicationData != null) {
                appOps.setMode(AppOpsManager.OP_WRITE_SMS, oldSmsApplicationData.mUid,
                        oldSmsApplicationData.mPackageName, AppOpsManager.MODE_IGNORED);
            }

            // Update the secure setting.
            Settings.Secure.putString(context.getContentResolver(),
                    Settings.Secure.SMS_DEFAULT_APPLICATION, smsApplicationData.mPackageName);

            // Allow OP_WRITE_SMS for the newly configured default SMS app.
            appOps.setMode(AppOpsManager.OP_WRITE_SMS, smsApplicationData.mUid,
                    smsApplicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
        }
    }

    /**
     * Returns SmsApplicationData for this package if this package is capable of being set as the
     * default SMS application.
     */
    public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
        Collection<SmsApplicationData> applications = getApplicationCollection(context);
        return getApplicationForPackage(applications, packageName);
    }

    /**
     * Gets the default SMS application
     * @param context context from the calling app
     * @param updateIfNeeded update the default app if there is no valid default app configured.
     * @return component name of the app and class to deliver SMS messages to
     */
    public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
        ComponentName component = null;
        SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded);
        if (smsApplicationData != null) {
            component = new ComponentName(smsApplicationData.mPackageName,
                    smsApplicationData.mSmsReceiverClass);
        }
        return component;
    }

    /**
     * Gets the default MMS application
     * @param context context from the calling app
     * @param updateIfNeeded update the default app if there is no valid default app configured.
     * @return component name of the app and class to deliver MMS messages to
     */
    public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
        ComponentName component = null;
        SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded);
        if (smsApplicationData != null) {
            component = new ComponentName(smsApplicationData.mPackageName,
                    smsApplicationData.mMmsReceiverClass);
        }
        return component;
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -216,7 +216,7 @@ public class WapPushOverSms implements ServiceConnection {
            appOp = AppOpsManager.OP_RECEIVE_WAP_PUSH;
        }

        Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION);
        Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
        intent.setType(mimeType);
        intent.putExtra("transactionId", transactionId);
        intent.putExtra("pduType", pduType);
@@ -224,6 +224,16 @@ public class WapPushOverSms implements ServiceConnection {
        intent.putExtra("data", intentData);
        intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters());

        // Direct the intent to only the default MMS app. If we can't find a default MMS app
        // then sent it to all broadcast receivers.
        ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true);
        if (componentName != null) {
            // Deliver MMS message only to this receiver
            intent.setComponent(componentName);
            if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
                  " " + componentName.getClassName());
        }

        handler.dispatchIntent(intent, permission, appOp, receiver);
        return Activity.RESULT_OK;
    }