OSDN Git Service

Initial implementation of IntentFirewall functionality
authorBen Gruver <bgruv@google.com>
Wed, 3 Apr 2013 04:18:41 +0000 (21:18 -0700)
committerBen Gruver <bgruv@google.com>
Wed, 3 Apr 2013 04:18:41 +0000 (21:18 -0700)
This has the full filter functionality, but is currently only
able to block Activity intents. Logging intents, or blocking
service/broadcast intents is not yet implemented.

Change-Id: Ied3d8dedf982e17bcbdff3e328eeb87477954df7

18 files changed:
services/java/com/android/server/IntentResolver.java
services/java/com/android/server/IntentResolverOld.java
services/java/com/android/server/am/ActivityManagerService.java
services/java/com/android/server/am/ActivityStack.java
services/java/com/android/server/firewall/AndFilter.java [new file with mode: 0644]
services/java/com/android/server/firewall/CategoryFilter.java [new file with mode: 0644]
services/java/com/android/server/firewall/Filter.java [new file with mode: 0644]
services/java/com/android/server/firewall/FilterFactory.java [new file with mode: 0644]
services/java/com/android/server/firewall/FilterList.java [new file with mode: 0644]
services/java/com/android/server/firewall/IntentFirewall.java [new file with mode: 0644]
services/java/com/android/server/firewall/NotFilter.java [new file with mode: 0644]
services/java/com/android/server/firewall/OrFilter.java [new file with mode: 0644]
services/java/com/android/server/firewall/PortFilter.java [new file with mode: 0644]
services/java/com/android/server/firewall/SenderFilter.java [new file with mode: 0644]
services/java/com/android/server/firewall/SenderPermissionFilter.java [new file with mode: 0644]
services/java/com/android/server/firewall/StringFilter.java [new file with mode: 0644]
services/java/com/android/server/pm/PackageManagerService.java
services/java/com/android/server/pm/PreferredIntentResolver.java

index 9b19008..35345f5 100644 (file)
@@ -117,7 +117,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
             boolean printedHeader = false;
             F filter;
             for (int i=0; i<N && (filter=a[i]) != null; i++) {
-                if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+                if (packageName != null && !isPackageForFilter(packageName, filter)) {
                     continue;
                 }
                 if (title != null) {
@@ -357,11 +357,11 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
     }
 
     /**
-     * Return the package that owns this filter.  This must be implemented to
-     * provide correct filtering of Intents that have specified a package name
-     * they are to be delivered to.
+     * Returns whether this filter is owned by this package. This must be
+     * implemented to provide correct filtering of Intents that have
+     * specified a package name they are to be delivered to.
      */
-    protected abstract String packageForFilter(F filter);
+    protected abstract boolean isPackageForFilter(String packageName, F filter);
 
     protected abstract F[] newArray(int size);
 
@@ -556,7 +556,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
             }
 
             // Is delivery being limited to filters owned by a particular package?
-            if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+            if (packageName != null && !isPackageForFilter(packageName, filter)) {
                 if (debug) {
                     Slog.v(TAG, "  Filter is not from package " + packageName + "; skipping");
                 }
@@ -710,8 +710,8 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
     }
 
     private final IntentResolverOld<F, R> mOldResolver = new IntentResolverOld<F, R>() {
-        @Override protected String packageForFilter(F filter) {
-            return IntentResolver.this.packageForFilter(filter);
+        @Override protected boolean isPackageForFilter(String packageName, F filter) {
+            return IntentResolver.this.isPackageForFilter(packageName, filter);
         }
         @Override protected boolean allowFilterResult(F filter, List<R> dest) {
             return IntentResolver.this.allowFilterResult(filter, dest);
index 4dd77ce..94a2379 100644 (file)
@@ -106,7 +106,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object
             boolean printedHeader = false;
             for (int i=0; i<N; i++) {
                 F filter = a.get(i);
-                if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+                if (packageName != null && !isPackageForFilter(packageName, filter)) {
                     continue;
                 }
                 if (title != null) {
@@ -336,11 +336,11 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object
     }
 
     /**
-     * Return the package that owns this filter.  This must be implemented to
-     * provide correct filtering of Intents that have specified a package name
-     * they are to be delivered to.
+     * Returns whether this filter is owned by this package. This must be
+     * implemented to provide correct filtering of Intents that have
+     * specified a package name they are to be delivered to.
      */
-    protected abstract String packageForFilter(F filter);
+    protected abstract boolean isPackageForFilter(String packageName, F filter);
     
     @SuppressWarnings("unchecked")
     protected R newResult(F filter, int match, int userId) {
@@ -529,7 +529,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object
             }
 
             // Is delivery being limited to filters owned by a particular package?
-            if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+            if (packageName != null && !isPackageForFilter(packageName, filter)) {
                 if (debug) {
                     Slog.v(TAG, "  Filter is not from package " + packageName + "; skipping");
                 }
index 88ef884..90a727d 100644 (file)
@@ -30,6 +30,7 @@ import com.android.server.ProcessMap;
 import com.android.server.SystemServer;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.AppTransition;
 import com.android.server.wm.WindowManagerService;
@@ -274,6 +275,8 @@ public final class ActivityManagerService  extends ActivityManagerNative
 
     public ActivityStack mMainStack;
 
+    public IntentFirewall mIntentFirewall;
+
     private final boolean mHeadless;
 
     // Whether we should show our dialogs (ANR, crash, etc) or just perform their
@@ -570,8 +573,8 @@ public final class ActivityManagerService  extends ActivityManagerNative
         }
 
         @Override
-        protected String packageForFilter(BroadcastFilter filter) {
-            return filter.packageName;
+        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+            return packageName.equals(filter.packageName);
         }
     };
 
@@ -1467,7 +1470,8 @@ public final class ActivityManagerService  extends ActivityManagerNative
         m.mContext = context;
         m.mFactoryTest = factoryTest;
         m.mMainStack = new ActivityStack(m, context, true, thr.mLooper);
-        
+        m.mIntentFirewall = new IntentFirewall(m.new IntentFirewallInterface());
+
         m.mBatteryStatsService.publish(context);
         m.mUsageStatsService.publish(context);
         m.mAppOpsService.publish(context);
@@ -4943,6 +4947,14 @@ public final class ActivityManagerService  extends ActivityManagerNative
         }
     }
 
+    class IntentFirewallInterface implements IntentFirewall.AMSInterface {
+        public int checkComponentPermission(String permission, int pid, int uid,
+                int owningUid, boolean exported) {
+            return ActivityManagerService.this.checkComponentPermission(permission, pid, uid,
+                    owningUid, exported);
+        }
+    }
+
     /**
      * This can be called with or without the global lock held.
      */
index 526b24f..3d7da7b 100644 (file)
@@ -2489,6 +2489,7 @@ final class ActivityStack {
         int err = ActivityManager.START_SUCCESS;
 
         ProcessRecord callerApp = null;
+
         if (caller != null) {
             callerApp = mService.getRecordForAppLocked(caller);
             if (callerApp != null) {
@@ -2592,34 +2593,37 @@ final class ActivityStack {
             throw new SecurityException(msg);
         }
 
+        boolean abort = !mService.mIntentFirewall.checkStartActivity(intent,
+                callerApp==null?null:callerApp.info, callingPackage, callingUid, callingPid,
+                resolvedType, aInfo);
+
         if (mMainStack) {
             if (mService.mController != null) {
-                boolean abort = false;
                 try {
                     // The Intent we give to the watcher has the extra data
                     // stripped off, since it can contain private information.
                     Intent watchIntent = intent.cloneFilter();
-                    abort = !mService.mController.activityStarting(watchIntent,
+                    abort |= !mService.mController.activityStarting(watchIntent,
                             aInfo.applicationInfo.packageName);
                 } catch (RemoteException e) {
                     mService.mController = null;
                 }
-    
-                if (abort) {
-                    if (resultRecord != null) {
-                        sendActivityResultLocked(-1,
-                            resultRecord, resultWho, requestCode,
-                            Activity.RESULT_CANCELED, null);
-                    }
-                    // We pretend to the caller that it was really started, but
-                    // they will just get a cancel result.
-                    mDismissKeyguardOnNextActivity = false;
-                    ActivityOptions.abort(options);
-                    return ActivityManager.START_SUCCESS;
-                }
             }
         }
 
+        if (abort) {
+            if (resultRecord != null) {
+                sendActivityResultLocked(-1,
+                    resultRecord, resultWho, requestCode,
+                    Activity.RESULT_CANCELED, null);
+            }
+            // We pretend to the caller that it was really started, but
+            // they will just get a cancel result.
+            mDismissKeyguardOnNextActivity = false;
+            ActivityOptions.abort(options);
+            return ActivityManager.START_SUCCESS;
+        }
+
         ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, callingPackage,
                 intent, resolvedType, aInfo, mService.mConfiguration,
                 resultRecord, resultWho, requestCode, componentSpecified);
diff --git a/services/java/com/android/server/firewall/AndFilter.java b/services/java/com/android/server/firewall/AndFilter.java
new file mode 100644 (file)
index 0000000..cabf00b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class AndFilter extends FilterList {
+    @Override
+    public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+            String callerPackage, int callerUid, int callerPid, String resolvedType,
+            ApplicationInfo resolvedApp) {
+        for (int i=0; i<children.size(); i++) {
+            if (!children.get(i).matches(ifw, intent, callerApp, callerPackage, callerUid,
+                    callerPid, resolvedType, resolvedApp)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("and") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return new AndFilter().readFromXml(parser);
+        }
+    };
+}
diff --git a/services/java/com/android/server/firewall/CategoryFilter.java b/services/java/com/android/server/firewall/CategoryFilter.java
new file mode 100644 (file)
index 0000000..d5e9fe8
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Set;
+
+class CategoryFilter implements Filter {
+    private static final String ATTR_NAME = "name";
+
+    private final String mCategoryName;
+
+    private CategoryFilter(String categoryName) {
+        mCategoryName = categoryName;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, String callerPackage,
+            int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+        Set<String> categories = intent.getCategories();
+        if (categories == null) {
+            return false;
+        }
+        return categories.contains(mCategoryName);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("category") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            String categoryName = parser.getAttributeValue(null, ATTR_NAME);
+            if (categoryName == null) {
+                throw new XmlPullParserException("Category name must be specified.",
+                        parser, null);
+            }
+            return new CategoryFilter(categoryName);
+        }
+    };
+}
diff --git a/services/java/com/android/server/firewall/Filter.java b/services/java/com/android/server/firewall/Filter.java
new file mode 100644 (file)
index 0000000..7639466
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+
+interface Filter {
+    /**
+     * Does the given intent + context info match this filter?
+     *
+     * @param ifw The IntentFirewall instance
+     * @param intent The intent being started/bound/broadcast
+     * @param callerApp An ApplicationInfo of an application in the caller's process. This may not
+ *                  be the specific app that is actually sending the intent. This also may be
+ *                  null, if the caller is the system process, or an unrecognized process (e.g.
+ *                  am start)
+     * @param callerPackage The package name of the component sending the intent. This value is
+*                      provided by the caller and might be forged/faked.
+     * @param callerUid
+     * @param callerPid
+     * @param resolvedType The resolved mime type of the intent
+     * @param resolvedApp The application that contains the resolved component that the intent is
+     */
+    boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+            String callerPackage, int callerUid, int callerPid, String resolvedType,
+            ApplicationInfo resolvedApp);
+}
diff --git a/services/java/com/android/server/firewall/FilterFactory.java b/services/java/com/android/server/firewall/FilterFactory.java
new file mode 100644 (file)
index 0000000..dea8b40
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.server.firewall;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public abstract class FilterFactory {
+    private final String mTag;
+
+    protected FilterFactory(String tag) {
+        if (tag == null) {
+            throw new NullPointerException();
+        }
+        mTag = tag;
+    }
+
+    public String getTagName() {
+        return mTag;
+    }
+
+    public abstract Filter newFilter(XmlPullParser parser)
+            throws IOException, XmlPullParserException;
+}
diff --git a/services/java/com/android/server/firewall/FilterList.java b/services/java/com/android/server/firewall/FilterList.java
new file mode 100644 (file)
index 0000000..d34b203
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.server.firewall;
+
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+abstract class FilterList implements Filter {
+    protected final ArrayList<Filter> children = new ArrayList<Filter>();
+
+    public FilterList readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            readChild(parser);
+        }
+        return this;
+    }
+
+    protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException {
+        Filter filter = IntentFirewall.parseFilter(parser);
+        children.add(filter);
+    }
+}
diff --git a/services/java/com/android/server/firewall/IntentFirewall.java b/services/java/com/android/server/firewall/IntentFirewall.java
new file mode 100644 (file)
index 0000000..ebbbd86
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.os.ServiceManager;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+import com.android.server.IntentResolver;
+import com.android.server.pm.PackageManagerService;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class IntentFirewall {
+    private static final String TAG = "IntentFirewall";
+
+    private static final String RULES_FILENAME = "ifw.xml";
+
+    private static final String TAG_RULES = "rules";
+    private static final String TAG_ACTIVITY = "activity";
+    private static final String TAG_SERVICE = "service";
+    private static final String TAG_BROADCAST = "broadcast";
+
+    private static final HashMap<String, FilterFactory> factoryMap;
+
+    private final AMSInterface mAms;
+
+    private final IntentResolver<FirewallIntentFilter, Rule> mActivityResolver =
+            new FirewallIntentResolver();
+    private final IntentResolver<FirewallIntentFilter, Rule> mServiceResolver =
+            new FirewallIntentResolver();
+    private final IntentResolver<FirewallIntentFilter, Rule> mBroadcastResolver =
+            new FirewallIntentResolver();
+
+    static {
+        FilterFactory[] factories = new FilterFactory[] {
+                AndFilter.FACTORY,
+                OrFilter.FACTORY,
+                NotFilter.FACTORY,
+
+                StringFilter.ACTION,
+                StringFilter.COMPONENT,
+                StringFilter.COMPONENT_NAME,
+                StringFilter.COMPONENT_PACKAGE,
+                StringFilter.DATA,
+                StringFilter.HOST,
+                StringFilter.MIME_TYPE,
+                StringFilter.PATH,
+                StringFilter.SENDER_PACKAGE,
+                StringFilter.SSP,
+
+                CategoryFilter.FACTORY,
+                SenderFilter.FACTORY,
+                SenderPermissionFilter.FACTORY,
+                PortFilter.FACTORY
+        };
+
+        // load factor ~= .75
+        factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3);
+        for (int i=0; i<factories.length; i++) {
+            FilterFactory factory = factories[i];
+            factoryMap.put(factory.getTagName(), factory);
+        }
+    }
+
+    public IntentFirewall(AMSInterface ams) {
+        mAms = ams;
+        File dataSystemDir = new File(Environment.getDataDirectory(), "system");
+        File rulesFile = new File(dataSystemDir, RULES_FILENAME);
+        readRules(rulesFile);
+    }
+
+    public boolean checkStartActivity(Intent intent, ApplicationInfo callerApp,
+            String callerPackage, int callerUid, int callerPid, String resolvedType,
+            ActivityInfo resolvedActivity) {
+        List<Rule> matchingRules = mActivityResolver.queryIntent(intent, resolvedType, false, 0);
+        boolean log = false;
+        boolean block = false;
+
+        for (int i=0; i< matchingRules.size(); i++) {
+            Rule rule = matchingRules.get(i);
+            if (rule.matches(this, intent, callerApp, callerPackage, callerUid, callerPid,
+                    resolvedType, resolvedActivity.applicationInfo)) {
+                block |= rule.getBlock();
+                log |= rule.getLog();
+
+                // if we've already determined that we should both block and log, there's no need
+                // to continue trying rules
+                if (block && log) {
+                    break;
+                }
+            }
+        }
+
+        if (log) {
+            // TODO: log info about intent to event log
+        }
+
+        return !block;
+    }
+
+    private void readRules(File rulesFile) {
+        FileInputStream fis;
+        try {
+            fis = new FileInputStream(rulesFile);
+        } catch (FileNotFoundException ex) {
+            // Nope, no rules. Nothing else to do!
+            return;
+        }
+
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+
+            parser.setInput(fis, null);
+
+            XmlUtils.beginDocument(parser, TAG_RULES);
+
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                IntentResolver<FirewallIntentFilter, Rule> resolver = null;
+                String tagName = parser.getName();
+                if (tagName.equals(TAG_ACTIVITY)) {
+                    resolver = mActivityResolver;
+                } else if (tagName.equals(TAG_SERVICE)) {
+                    resolver = mServiceResolver;
+                } else if (tagName.equals(TAG_BROADCAST)) {
+                    resolver = mBroadcastResolver;
+                }
+
+                if (resolver != null) {
+                    Rule rule = new Rule();
+
+                    try {
+                        rule.readFromXml(parser);
+                    } catch (XmlPullParserException ex) {
+                        Slog.e(TAG, "Error reading intent firewall rule", ex);
+                        continue;
+                    } catch (IOException ex) {
+                        Slog.e(TAG, "Error reading intent firewall rule", ex);
+                        continue;
+                    }
+
+                    for (int i=0; i<rule.getIntentFilterCount(); i++) {
+                        resolver.addFilter(rule.getIntentFilter(i));
+                    }
+                }
+            }
+        } catch (XmlPullParserException ex) {
+            Slog.e(TAG, "Error reading intent firewall rules", ex);
+        } catch (IOException ex) {
+            Slog.e(TAG, "Error reading intent firewall rules", ex);
+        } finally {
+            try {
+                fis.close();
+            } catch (IOException ex) {
+                Slog.e(TAG, "Error while closing " + rulesFile, ex);
+            }
+        }
+    }
+
+    static Filter parseFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
+        String elementName = parser.getName();
+
+        FilterFactory factory = factoryMap.get(elementName);
+
+        if (factory == null) {
+            throw new XmlPullParserException("Unknown element in filter list: " + elementName);
+        }
+        return factory.newFilter(parser);
+    }
+
+    private static class Rule extends AndFilter {
+        private static final String TAG_INTENT_FILTER = "intent-filter";
+
+        private static final String ATTR_BLOCK = "block";
+        private static final String ATTR_LOG = "log";
+
+        private final ArrayList<FirewallIntentFilter> mIntentFilters =
+                new ArrayList<FirewallIntentFilter>(1);
+        private boolean block;
+        private boolean log;
+
+        @Override
+        public Rule readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+            block = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_BLOCK));
+            log = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_LOG));
+
+            super.readFromXml(parser);
+            return this;
+        }
+
+        @Override
+        protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException {
+            if (parser.getName().equals(TAG_INTENT_FILTER)) {
+                FirewallIntentFilter intentFilter = new FirewallIntentFilter(this);
+                intentFilter.readFromXml(parser);
+                mIntentFilters.add(intentFilter);
+            } else {
+                super.readChild(parser);
+            }
+        }
+
+        public int getIntentFilterCount() {
+            return mIntentFilters.size();
+        }
+
+        public FirewallIntentFilter getIntentFilter(int index) {
+            return mIntentFilters.get(index);
+        }
+
+        public boolean getBlock() {
+            return block;
+        }
+
+        public boolean getLog() {
+            return log;
+        }
+    }
+
+    private static class FirewallIntentFilter extends IntentFilter {
+        private final Rule rule;
+
+        public FirewallIntentFilter(Rule rule) {
+            this.rule = rule;
+        }
+    }
+
+    private static class FirewallIntentResolver
+            extends IntentResolver<FirewallIntentFilter, Rule> {
+        @Override
+        protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) {
+            return !dest.contains(filter.rule);
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) {
+            return true;
+        }
+
+        @Override
+        protected FirewallIntentFilter[] newArray(int size) {
+            return new FirewallIntentFilter[size];
+        }
+
+        @Override
+        protected Rule newResult(FirewallIntentFilter filter, int match, int userId) {
+            return filter.rule;
+        }
+
+        @Override
+        protected void sortResults(List<Rule> results) {
+            // there's no need to sort the results
+            return;
+        }
+    }
+
+    /**
+     * This interface contains the methods we need from ActivityManagerService. This allows AMS to
+     * export these methods to us without making them public, and also makes it easier to test this
+     * component.
+     */
+    public interface AMSInterface {
+        int checkComponentPermission(String permission, int pid, int uid,
+                int owningUid, boolean exported);
+    }
+
+    /**
+     * Checks if the caller has access to a component
+     *
+     * @param permission If present, the caller must have this permission
+     * @param pid The pid of the caller
+     * @param uid The uid of the caller
+     * @param owningUid The uid of the application that owns the component
+     * @param exported Whether the component is exported
+     * @return True if the caller can access the described component
+     */
+    boolean checkComponentPermission(String permission, int pid, int uid, int owningUid,
+            boolean exported) {
+        return mAms.checkComponentPermission(permission, pid, uid, owningUid, exported) ==
+                PackageManager.PERMISSION_GRANTED;
+    }
+
+    boolean signaturesMatch(int uid1, int uid2) {
+        PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
+        return pm.checkUidSignatures(uid1, uid2) == PackageManager.SIGNATURE_MATCH;
+    }
+}
diff --git a/services/java/com/android/server/firewall/NotFilter.java b/services/java/com/android/server/firewall/NotFilter.java
new file mode 100644 (file)
index 0000000..2ff108a
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class NotFilter implements Filter {
+    private final Filter mChild;
+
+    private NotFilter(Filter child) {
+        mChild = child;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+            String callerPackage, int callerUid, int callerPid, String resolvedType,
+            ApplicationInfo resolvedApp) {
+        return !mChild.matches(ifw, intent, callerApp, callerPackage, callerUid, callerPid,
+                resolvedType, resolvedApp);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("not") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            Filter child = null;
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                Filter filter = IntentFirewall.parseFilter(parser);
+                if (child == null) {
+                    child = filter;
+                } else {
+                    throw new XmlPullParserException(
+                            "<not> tag can only contain a single child filter.", parser, null);
+                }
+            }
+            return new NotFilter(child);
+        }
+    };
+}
diff --git a/services/java/com/android/server/firewall/OrFilter.java b/services/java/com/android/server/firewall/OrFilter.java
new file mode 100644 (file)
index 0000000..1ed1c85
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class OrFilter extends FilterList {
+    @Override
+    public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+            String callerPackage, int callerUid, int callerPid, String resolvedType,
+            ApplicationInfo resolvedApp) {
+        for (int i=0; i<children.size(); i++) {
+            if (children.get(i).matches(ifw, intent, callerApp, callerPackage, callerUid, callerPid,
+                    resolvedType, resolvedApp)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("or") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return new OrFilter().readFromXml(parser);
+        }
+    };
+}
diff --git a/services/java/com/android/server/firewall/PortFilter.java b/services/java/com/android/server/firewall/PortFilter.java
new file mode 100644 (file)
index 0000000..2b2a198
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class PortFilter implements Filter {
+    private static final String ATTR_EQUALS = "equals";
+    private static final String ATTR_MIN = "min";
+    private static final String ATTR_MAX = "max";
+
+    private static final int NO_BOUND = -1;
+
+    // both bounds are inclusive
+    private final int mLowerBound;
+    private final int mUpperBound;
+
+    private PortFilter(int lowerBound, int upperBound) {
+        mLowerBound = lowerBound;
+        mUpperBound = upperBound;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+            String callerPackage, int callerUid, int callerPid, String resolvedType,
+            ApplicationInfo resolvedApp) {
+        int port = -1;
+        Uri uri = intent.getData();
+        if (uri != null) {
+            port = uri.getPort();
+        }
+        return port != -1 &&
+                (mLowerBound == NO_BOUND || mLowerBound <= port) &&
+                (mUpperBound == NO_BOUND || mUpperBound >= port);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("port") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            int lowerBound = NO_BOUND;
+            int upperBound = NO_BOUND;
+
+            String equalsValue = parser.getAttributeValue(null, ATTR_EQUALS);
+            if (equalsValue != null) {
+                int value;
+                try {
+                    value = Integer.parseInt(equalsValue);
+                } catch (NumberFormatException ex) {
+                    throw new XmlPullParserException("Invalid port value: " + equalsValue,
+                            parser, null);
+                }
+                lowerBound = value;
+                upperBound = value;
+            }
+
+            String lowerBoundString = parser.getAttributeValue(null, ATTR_MIN);
+            String upperBoundString = parser.getAttributeValue(null, ATTR_MAX);
+            if (lowerBoundString != null || upperBoundString != null) {
+                if (equalsValue != null) {
+                    throw new XmlPullParserException(
+                            "Port filter cannot use both equals and range filtering",
+                            parser, null);
+                }
+
+                if (lowerBoundString != null) {
+                    try {
+                        lowerBound = Integer.parseInt(lowerBoundString);
+                    } catch (NumberFormatException ex) {
+                        throw new XmlPullParserException(
+                                "Invalid minimum port value: " + lowerBoundString,
+                                parser, null);
+                    }
+                }
+
+                if (upperBoundString != null) {
+                    try {
+                        upperBound = Integer.parseInt(upperBoundString);
+                    } catch (NumberFormatException ex) {
+                        throw new XmlPullParserException(
+                                "Invalid maximum port value: " + upperBoundString,
+                                parser, null);
+                    }
+                }
+            }
+
+            // an empty port filter is explicitly allowed, and checks for the existence of a port
+            return new PortFilter(lowerBound, upperBound);
+        }
+    };
+}
diff --git a/services/java/com/android/server/firewall/SenderFilter.java b/services/java/com/android/server/firewall/SenderFilter.java
new file mode 100644 (file)
index 0000000..0b790bd
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class SenderFilter {
+    private static final String ATTR_TYPE = "type";
+
+    private static final String VAL_SIGNATURE = "signature";
+    private static final String VAL_SYSTEM = "system";
+    private static final String VAL_SYSTEM_OR_SIGNATURE = "system|signature";
+    private static final String VAL_USER_ID = "userId";
+
+    static boolean isSystemApp(ApplicationInfo callerApp, int callerUid, int callerPid) {
+        if (callerUid == Process.SYSTEM_UID ||
+                callerPid == Process.myPid()) {
+            return true;
+        }
+        if (callerApp == null) {
+            return false;
+        }
+        return (callerApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("sender") {
+        @Override
+        public Filter newFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
+            String typeString = parser.getAttributeValue(null, ATTR_TYPE);
+            if (typeString == null) {
+                throw new XmlPullParserException("type attribute must be specified for <sender>",
+                        parser, null);
+            }
+            if (typeString.equals(VAL_SYSTEM)) {
+                return SYSTEM;
+            } else if (typeString.equals(VAL_SIGNATURE)) {
+                return SIGNATURE;
+            } else if (typeString.equals(VAL_SYSTEM_OR_SIGNATURE)) {
+                return SYSTEM_OR_SIGNATURE;
+            } else if (typeString.equals(VAL_USER_ID)) {
+                return USER_ID;
+            }
+            throw new XmlPullParserException(
+                    "Invalid type attribute for <sender>: " + typeString, parser, null);
+        }
+    };
+
+    private static final Filter SIGNATURE = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+                String callerPackage, int callerUid, int callerPid, String resolvedType,
+                ApplicationInfo resolvedApp) {
+            if (callerApp == null) {
+                return false;
+            }
+            return ifw.signaturesMatch(callerUid, resolvedApp.uid);
+        }
+    };
+
+    private static final Filter SYSTEM = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+                String callerPackage, int callerUid, int callerPid, String resolvedType,
+                ApplicationInfo resolvedApp) {
+            if (callerApp == null) {
+                // if callerApp is null, the caller is the system process
+                return false;
+            }
+            return isSystemApp(callerApp, callerUid, callerPid);
+        }
+    };
+
+    private static final Filter SYSTEM_OR_SIGNATURE = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+                String callerPackage, int callerUid, int callerPid, String resolvedType,
+                ApplicationInfo resolvedApp) {
+            return isSystemApp(callerApp, callerUid, callerPid) ||
+                    ifw.signaturesMatch(callerUid, resolvedApp.uid);
+        }
+    };
+
+    private static final Filter USER_ID = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+                String callerPackage, int callerUid, int callerPid, String resolvedType,
+                ApplicationInfo resolvedApp) {
+            // This checks whether the caller is either the system process, or has the same user id
+            // I.e. the same app, or an app that uses the same shared user id.
+            // This is the same set of applications that would be able to access the component if
+            // it wasn't exported.
+            return ifw.checkComponentPermission(null, callerPid, callerUid, resolvedApp.uid, false);
+        }
+    };
+}
diff --git a/services/java/com/android/server/firewall/SenderPermissionFilter.java b/services/java/com/android/server/firewall/SenderPermissionFilter.java
new file mode 100644 (file)
index 0000000..02d8b15
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.server.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class SenderPermissionFilter implements Filter {
+    private static final String ATTR_NAME = "name";
+
+    private final String mPermission;
+
+    private SenderPermissionFilter(String permission) {
+        mPermission = permission;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+            String callerPackage, int callerUid, int callerPid, String resolvedType,
+            ApplicationInfo resolvedApp) {
+        // We assume the component is exported here. If the component is not exported, then
+        // ActivityManager would only resolve to this component for callers from the same uid.
+        // In this case, it doesn't matter whether the component is exported or not.
+        return ifw.checkComponentPermission(mPermission, callerPid, callerUid, resolvedApp.uid,
+                true);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("sender-permission") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            String permission = parser.getAttributeValue(null, ATTR_NAME);
+            if (permission == null) {
+                throw new XmlPullParserException("Permission name must be specified.",
+                        parser, null);
+            }
+            return new SenderPermissionFilter(permission);
+        }
+    };
+}
diff --git a/services/java/com/android/server/firewall/StringFilter.java b/services/java/com/android/server/firewall/StringFilter.java
new file mode 100644 (file)
index 0000000..de5a69f
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * 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.server.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.PatternMatcher;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+abstract class StringFilter implements Filter {
+    private static final String ATTR_EQUALS = "equals";
+    private static final String ATTR_STARTS_WITH = "startsWith";
+    private static final String ATTR_CONTAINS = "contains";
+    private static final String ATTR_PATTERN = "pattern";
+    private static final String ATTR_REGEX = "regex";
+    private static final String ATTR_IS_NULL = "isNull";
+
+    private final ValueProvider mValueProvider;
+
+    private StringFilter(ValueProvider valueProvider) {
+        this.mValueProvider = valueProvider;
+    }
+
+    /**
+     * Constructs a new StringFilter based on the string filter attribute on the current
+     * element, and the given StringValueMatcher.
+     *
+     * The current node should contain exactly 1 string filter attribute. E.g. equals,
+     * contains, etc. Otherwise, an XmlPullParserException will be thrown.
+     *
+     * @param parser      An XmlPullParser object positioned at an element that should
+     *                    contain a string filter attribute
+     * @return This StringFilter object
+     */
+    public static StringFilter readFromXml(ValueProvider valueProvider, XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        StringFilter filter = null;
+
+        for (int i=0; i<parser.getAttributeCount(); i++) {
+            StringFilter newFilter = getFilter(valueProvider, parser, i);
+            if (newFilter != null) {
+                if (filter != null) {
+                    throw new XmlPullParserException("Multiple string filter attributes found");
+                }
+                filter = newFilter;
+            }
+        }
+
+        if (filter == null) {
+            // if there are no string filter attributes, we default to isNull="false" so that an
+            // empty filter is equivalent to an existence check
+            filter = new IsNullFilter(valueProvider, false);
+        }
+
+        return filter;
+    }
+
+    private static StringFilter getFilter(ValueProvider valueProvider, XmlPullParser parser,
+            int attributeIndex) {
+        String attributeName = parser.getAttributeName(attributeIndex);
+
+        switch (attributeName.charAt(0)) {
+            case 'e':
+                if (!attributeName.equals(ATTR_EQUALS)) {
+                    return null;
+                }
+                return new EqualsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+            case 'i':
+                if (!attributeName.equals(ATTR_IS_NULL)) {
+                    return null;
+                }
+                return new IsNullFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+            case 's':
+                if (!attributeName.equals(ATTR_STARTS_WITH)) {
+                    return null;
+                }
+                return new StartsWithFilter(valueProvider,
+                        parser.getAttributeValue(attributeIndex));
+            case 'c':
+                if (!attributeName.equals(ATTR_CONTAINS)) {
+                    return null;
+                }
+                return new ContainsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+            case 'p':
+                if (!attributeName.equals(ATTR_PATTERN)) {
+                    return null;
+                }
+                return new PatternStringFilter(valueProvider,
+                        parser.getAttributeValue(attributeIndex));
+            case 'r':
+                if (!attributeName.equals(ATTR_REGEX)) {
+                    return null;
+                }
+                return new RegexFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+        }
+        return null;
+    }
+
+    protected abstract boolean matchesValue(String value);
+
+    @Override
+    public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, String callerPackage,
+            int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+        String value = mValueProvider.getValue(intent, callerApp, callerPackage, resolvedType,
+                resolvedApp);
+        return matchesValue(value);
+    }
+
+    private static abstract class ValueProvider extends FilterFactory {
+        protected ValueProvider(String tag) {
+            super(tag);
+        }
+
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return StringFilter.readFromXml(this, parser);
+        }
+
+        public abstract String getValue(Intent intent, ApplicationInfo callerApp,
+                String callerPackage, String resolvedType, ApplicationInfo resolvedApp);
+    }
+
+    private static class EqualsFilter extends StringFilter {
+        private final String mFilterValue;
+
+        public EqualsFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mFilterValue = attrValue;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && value.equals(mFilterValue);
+        }
+    }
+
+    private static class ContainsFilter extends StringFilter {
+        private final String mFilterValue;
+
+        public ContainsFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mFilterValue = attrValue;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && value.contains(mFilterValue);
+        }
+    }
+
+    private static class StartsWithFilter extends StringFilter {
+        private final String mFilterValue;
+
+        public StartsWithFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mFilterValue = attrValue;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && value.startsWith(mFilterValue);
+        }
+    }
+
+    private static class PatternStringFilter extends StringFilter {
+        private final PatternMatcher mPattern;
+
+        public PatternStringFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mPattern = new PatternMatcher(attrValue, PatternMatcher.PATTERN_SIMPLE_GLOB);
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && mPattern.match(value);
+        }
+    }
+
+    private static class RegexFilter extends StringFilter {
+        private final Pattern mPattern;
+
+        public RegexFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            this.mPattern = Pattern.compile(attrValue);
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && mPattern.matcher(value).matches();
+        }
+    }
+
+    private static class IsNullFilter extends StringFilter {
+        private final boolean mIsNull;
+
+        public IsNullFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mIsNull = Boolean.parseBoolean(attrValue);
+        }
+
+        public IsNullFilter(ValueProvider valueProvider, boolean isNull) {
+            super(valueProvider);
+            mIsNull = isNull;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return (value == null) == mIsNull;
+        }
+    }
+
+    public static final ValueProvider COMPONENT = new ValueProvider("component") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            ComponentName cn = intent.getComponent();
+            if (cn != null) {
+                return cn.flattenToString();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            ComponentName cn = intent.getComponent();
+            if (cn != null) {
+                return cn.getClassName();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            ComponentName cn = intent.getComponent();
+            if (cn != null) {
+                return cn.getPackageName();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider SENDER_PACKAGE = new ValueProvider("sender-package") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            // TODO: We can't trust this value, so maybe should check all packages in the caller process?
+            return callerPackage;
+        }
+    };
+
+
+    public static final FilterFactory ACTION = new ValueProvider("action") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            return intent.getAction();
+        }
+    };
+
+    public static final ValueProvider DATA = new ValueProvider("data") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.toString();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            return resolvedType;
+        }
+    };
+
+    public static final ValueProvider SCHEME = new ValueProvider("scheme") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getScheme();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getSchemeSpecificPart();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider HOST = new ValueProvider("host") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getHost();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider PATH = new ValueProvider("path") {
+        @Override
+        public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage,
+                String resolvedType, ApplicationInfo resolvedApp) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getPath();
+            }
+            return null;
+        }
+    };
+}
index afdd294..30d98eb 100644 (file)
@@ -5383,8 +5383,9 @@ public class PackageManagerService extends IPackageManager.Stub {
         }
 
         @Override
-        protected String packageForFilter(PackageParser.ActivityIntentInfo info) {
-            return info.activity.owner.packageName;
+        protected boolean isPackageForFilter(String packageName,
+                PackageParser.ActivityIntentInfo info) {
+            return packageName.equals(info.activity.owner.packageName);
         }
         
         @Override
@@ -5580,8 +5581,9 @@ public class PackageManagerService extends IPackageManager.Stub {
         }
 
         @Override
-        protected String packageForFilter(PackageParser.ServiceIntentInfo info) {
-            return info.service.owner.packageName;
+        protected boolean isPackageForFilter(String packageName,
+                PackageParser.ServiceIntentInfo info) {
+            return packageName.equals(info.service.owner.packageName);
         }
         
         @Override
index 3f1e50c..7fe6a05 100644 (file)
@@ -27,8 +27,8 @@ public class PreferredIntentResolver
         return new PreferredActivity[size];
     }
     @Override
-    protected String packageForFilter(PreferredActivity filter) {
-        return filter.mPref.mComponent.getPackageName();
+    protected boolean isPackageForFilter(String packageName, PreferredActivity filter) {
+        return packageName.equals(filter.mPref.mComponent.getPackageName());
     }
     @Override
     protected void dumpFilter(PrintWriter out, String prefix,