Update preferred activity for SMS related SENDTO intents
David Braun [Tue, 12 Nov 2013 21:38:52 +0000 (13:38 -0800)]
Updates the preferred SENDTO activity for sms/mms schemes so that the user
will not see an intent disambiguation dialog for SENDTO sms/mms.

- Update preferred activity on set default SMS app
- Update preferred activity on "secure" get default SMS app
- Update preferred activity on package install/uninstall

Bug:11482259 When default SMS app changes we need to update the default app for the SEND intent
Change-Id: Ib2752fe84389f1c50cf2aa5841e75c3913b94e18

src/java/com/android/internal/telephony/PhoneFactory.java
src/java/com/android/internal/telephony/SmsApplication.java

index b9a6975..d7b4c8f 100644 (file)
@@ -147,6 +147,9 @@ public class PhoneFactory {
                 }
                 Rlog.i(LOG_TAG, "defaultSmsApplication: " + packageName);
 
+                // Set up monitor to watch for changes to SMS packages
+                SmsApplication.initSmsPackageMonitor(context);
+
                 sMadeDefaults = true;
             }
         }
index 05a12b3..cc69cca 100644 (file)
@@ -21,6 +21,7 @@ import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -33,6 +34,7 @@ import android.provider.Settings;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
+import com.android.internal.content.PackageMonitor;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -48,6 +50,13 @@ public final class SmsApplication {
     private static final String PHONE_PACKAGE_NAME = "com.android.phone";
     private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
 
+    private static final String SCHEME_SMS = "sms";
+    private static final String SCHEME_SMSTO = "smsto";
+    private static final String SCHEME_MMS = "mms";
+    private static final String SCHEME_MMSTO = "mmsto";
+
+    private static SmsPackageMonitor sSmsPackageMonitor = null;
+
     public static class SmsApplicationData {
         /**
          * Name of this SMS app for display.
@@ -168,7 +177,7 @@ public final class SmsApplication {
 
         // Update any existing entries with respond via message intent class.
         intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
-                Uri.fromParts("smsto", "", null));
+                Uri.fromParts(SCHEME_SMSTO, "", null));
         List<ResolveInfo> respondServices = packageManager.queryIntentServices(intent, 0);
         for (ResolveInfo resolveInfo : respondServices) {
             final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
@@ -187,7 +196,7 @@ public final class SmsApplication {
 
         // Update any existing entries with supports send to.
         intent = new Intent(Intent.ACTION_SENDTO,
-                Uri.fromParts("smsto", "", null));
+                Uri.fromParts(SCHEME_SMSTO, "", null));
         List<ResolveInfo> sendToActivities = packageManager.queryIntentActivities(intent, 0);
         for (ResolveInfo resolveInfo : sendToActivities) {
             final ActivityInfo activityInfo = resolveInfo.activityInfo;
@@ -311,8 +320,15 @@ public final class SmsApplication {
 
             // We can only verify the phone and BT app's permissions from a privileged caller
             if (updateIfNeeded) {
-                // Verify that the phone and BT app has permissions
+                // Ensure this component is still configured as the preferred activity. Usually the
+                // current SMS app will already be the preferred activity - but checking whether or
+                // not this is true is just as expensive as reconfiguring the preferred activity so
+                // we just reconfigure every time.
                 PackageManager packageManager = context.getPackageManager();
+                configurePreferredActivity(packageManager, new ComponentName(
+                        applicationData.mPackageName, applicationData.mSendToClass));
+
+                // Verify that the phone and BT app has permissions
                 try {
                     PackageInfo info = packageManager.getPackageInfo(PHONE_PACKAGE_NAME, 0);
                     int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
@@ -388,6 +404,10 @@ public final class SmsApplication {
             Settings.Secure.putString(context.getContentResolver(),
                     Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName);
 
+            // Configure this as the preferred activity for SENDTO sms/mms intents
+            configurePreferredActivity(packageManager, new ComponentName(
+                    applicationData.mPackageName, applicationData.mSendToClass));
+
             // Allow OP_WRITE_SMS for the newly configured default SMS app.
             appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
                     applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
@@ -415,6 +435,82 @@ public final class SmsApplication {
     }
 
     /**
+     * Tracks package changes and ensures that the default SMS app is always configured to be the
+     * preferred activity for SENDTO sms/mms intents.
+     */
+    private static final class SmsPackageMonitor extends PackageMonitor {
+        final Context mContext;
+
+        public SmsPackageMonitor(Context context) {
+            super();
+            mContext = context;
+        }
+
+        @Override
+        public void onPackageDisappeared(String packageName, int reason) {
+            onPackageChanged(packageName);
+        }
+
+        @Override
+        public void onPackageAppeared(String packageName, int reason) {
+            onPackageChanged(packageName);
+        }
+
+        @Override
+        public void onPackageModified(String packageName) {
+            onPackageChanged(packageName);
+        }
+
+        private void onPackageChanged(String packageName) {
+            PackageManager packageManager = mContext.getPackageManager();
+            // Ensure this component is still configured as the preferred activity
+            ComponentName componentName = getDefaultSendToApplication(mContext, true);
+            configurePreferredActivity(packageManager, componentName);
+        }
+    }
+
+    public static void initSmsPackageMonitor(Context context) {
+        sSmsPackageMonitor = new SmsPackageMonitor(context);
+        sSmsPackageMonitor.register(context, context.getMainLooper(), false);
+    }
+
+    private static void configurePreferredActivity(PackageManager packageManager,
+            ComponentName componentName) {
+        // Add the four activity preferences we want to direct to this app.
+        replacePreferredActivity(packageManager, componentName, SCHEME_SMS);
+        replacePreferredActivity(packageManager, componentName, SCHEME_SMSTO);
+        replacePreferredActivity(packageManager, componentName, SCHEME_MMS);
+        replacePreferredActivity(packageManager, componentName, SCHEME_MMSTO);}
+
+    /**
+     * Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
+     */
+    private static void replacePreferredActivity(PackageManager packageManager,
+            ComponentName componentName, String scheme) {
+        // Build the set of existing activities that handle this scheme
+        Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
+        List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(
+                intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER);
+
+        // Build the set of ComponentNames for these activities
+        final int n = resolveInfoList.size();
+        ComponentName[] set = new ComponentName[n];
+        for (int i = 0; i < n; i++) {
+            ResolveInfo info = resolveInfoList.get(i);
+            set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+        }
+
+        // Update the preferred SENDTO activity for the specified scheme
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SENDTO);
+        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+        intentFilter.addDataScheme(scheme);
+        packageManager.replacePreferredActivity(intentFilter,
+                IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
+                set, componentName);
+    }
+
+    /**
      * Returns SmsApplicationData for this package if this package is capable of being set as the
      * default SMS application.
      */