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

Commit b09491f2 authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Add new facility for apps to declared their preferred intents.

This is an extension from the existing data/etc/perferred-apps
facility.  Now applications pre-installed on the system image
can declare which intents they would like to be considered the
preferred app for.  When the system firsts initializes, or the
application settings are reset, these are used to configured
the current preferred app settings appropriately.

You use this with a new <preferred> tag under your activity,
which indicates which intents you would like to be the preferred
handler for.  The syntax for this is written much like an
intent filter, however semantically it is not really an intent
filter and so has some important differences:

- You can not use globbing patterns (for SSPs or paths).
- You can use only one action (if you use more than one it
  will only use the first one, so be careful).

Semantically what this is actually used for is a template
from which to generate a set of Intent objects, which are used
to probe the current environment in order to see if there are
multiple activities that can handle the Intent and, if so,
generate a new preferred setting for that pointing to your app.

As an example, here is how the preferred tag might be written
for the Maps application:

            <preferred>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="http" />
                <data android:scheme="https" />
                <data android:host="maps.google.com" />
                <data android:path="/" />
                <data android:pathPrefix="/maps" />
            </preferred>
            <preferred>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="geo" />
            </preferred>

From this, we generate the following set of potential Intents
to be matched, all with ACTION_VIEW, CATEGORY_DEFAULT+CATEGORY_BROWSABLE:

Change-Id: I7fd42aec8b6109c7dd20012529662362f1b7437a
http://maps.google.com/
http://maps.google.com/maps
https://maps.google.com/
https://maps.google.com/maps
geo:
parent 450d8c5b
Loading
Loading
Loading
Loading
+19 −4
Original line number Diff line number Diff line
@@ -733,10 +733,15 @@ public class IntentFilter implements Parcelable {
     * @see #addDataScheme
     */
    public final void addDataSchemeSpecificPart(String ssp, int type) {
        addDataSchemeSpecificPart(new PatternMatcher(ssp, type));
    }

    /** @hide */
    public final void addDataSchemeSpecificPart(PatternMatcher ssp) {
        if (mDataSchemeSpecificParts == null) {
            mDataSchemeSpecificParts = new ArrayList<PatternMatcher>();
        }
        mDataSchemeSpecificParts.add(new PatternMatcher(ssp, type));
        mDataSchemeSpecificParts.add(ssp);
    }

    /**
@@ -806,10 +811,15 @@ public class IntentFilter implements Parcelable {
     * @see #addDataScheme
     */
    public final void addDataAuthority(String host, String port) {
        if (port != null) port = port.intern();
        addDataAuthority(new AuthorityEntry(host.intern(), port));
    }

    /** @hide */
    public final void addDataAuthority(AuthorityEntry ent) {
        if (mDataAuthorities == null) mDataAuthorities =
                new ArrayList<AuthorityEntry>();
        if (port != null) port = port.intern();
        mDataAuthorities.add(new AuthorityEntry(host.intern(), port));
        mDataAuthorities.add(ent);
    }

    /**
@@ -874,8 +884,13 @@ public class IntentFilter implements Parcelable {
     * @see #addDataAuthority
     */
    public final void addDataPath(String path, int type) {
        addDataPath(new PatternMatcher(path.intern(), type));
    }

    /** @hide */
    public final void addDataPath(PatternMatcher path) {
        if (mDataPaths == null) mDataPaths = new ArrayList<PatternMatcher>();
        mDataPaths.add(new PatternMatcher(path.intern(), type));
        mDataPaths.add(path);
    }

    /**
+31 −9
Original line number Diff line number Diff line
@@ -45,9 +45,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertPath;
import java.security.cert.X509Certificate;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
@@ -2440,7 +2437,7 @@ public class PackageParser {

            if (parser.getName().equals("intent-filter")) {
                ActivityIntentInfo intent = new ActivityIntentInfo(a);
                if (!parseIntent(res, parser, attrs, flags, intent, outError, !receiver)) {
                if (!parseIntent(res, parser, attrs, true, intent, outError)) {
                    return null;
                }
                if (intent.countActions() == 0) {
@@ -2450,6 +2447,21 @@ public class PackageParser {
                } else {
                    a.intents.add(intent);
                }
            } else if (!receiver && parser.getName().equals("preferred")) {
                ActivityIntentInfo intent = new ActivityIntentInfo(a);
                if (!parseIntent(res, parser, attrs, false, intent, outError)) {
                    return null;
                }
                if (intent.countActions() == 0) {
                    Slog.w(TAG, "No actions in preferred at "
                            + mArchiveSourcePath + " "
                            + parser.getPositionDescription());
                } else {
                    if (owner.preferredActivityFilters == null) {
                        owner.preferredActivityFilters = new ArrayList<ActivityIntentInfo>();
                    }
                    owner.preferredActivityFilters.add(intent);
                }
            } else if (parser.getName().equals("meta-data")) {
                if ((a.metaData=parseMetaData(res, parser, attrs, a.metaData,
                        outError)) == null) {
@@ -2613,7 +2625,7 @@ public class PackageParser {

            if (parser.getName().equals("intent-filter")) {
                ActivityIntentInfo intent = new ActivityIntentInfo(a);
                if (!parseIntent(res, parser, attrs, flags, intent, outError, true)) {
                if (!parseIntent(res, parser, attrs, true, intent, outError)) {
                    return null;
                }
                if (intent.countActions() == 0) {
@@ -3037,7 +3049,7 @@ public class PackageParser {

            if (parser.getName().equals("intent-filter")) {
                ServiceIntentInfo intent = new ServiceIntentInfo(s);
                if (!parseIntent(res, parser, attrs, flags, intent, outError, false)) {
                if (!parseIntent(res, parser, attrs, true, intent, outError)) {
                    return null;
                }

@@ -3234,9 +3246,8 @@ public class PackageParser {
    private static final String ANDROID_RESOURCES
            = "http://schemas.android.com/apk/res/android";

    private boolean parseIntent(Resources res,
            XmlPullParser parser, AttributeSet attrs, int flags,
            IntentInfo outInfo, String[] outError, boolean isActivity)
    private boolean parseIntent(Resources res, XmlPullParser parser, AttributeSet attrs,
            boolean allowGlobs, IntentInfo outInfo, String[] outError)
            throws XmlPullParserException, IOException {

        TypedArray sa = res.obtainAttributes(attrs,
@@ -3327,6 +3338,10 @@ public class PackageParser {
                str = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestData_sspPattern, 0);
                if (str != null) {
                    if (!allowGlobs) {
                        outError[0] = "sspPattern not allowed here; ssp must be literal";
                        return false;
                    }
                    outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
                }

@@ -3353,6 +3368,10 @@ public class PackageParser {
                str = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestData_pathPattern, 0);
                if (str != null) {
                    if (!allowGlobs) {
                        outError[0] = "pathPattern not allowed here; path must be literal";
                        return false;
                    }
                    outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
                }

@@ -3414,6 +3433,8 @@ public class PackageParser {
        public ArrayList<String> usesOptionalLibraries = null;
        public String[] usesLibraryFiles = null;

        public ArrayList<ActivityIntentInfo> preferredActivityFilters = null;

        public ArrayList<String> mOriginalPackages = null;
        public String mRealPackage = null;
        public ArrayList<String> mAdoptPermissions = null;
@@ -4020,6 +4041,7 @@ public class PackageParser {
        public CharSequence nonLocalizedLabel;
        public int icon;
        public int logo;
        public int preferred;
    }

    public final static class ActivityIntentInfo extends IntentInfo {
+176 −82
Original line number Diff line number Diff line
@@ -1830,6 +1830,20 @@ final class Settings {
    }

    void readDefaultPreferredAppsLPw(PackageManagerService service, int userId) {
        // First pull data from any pre-installed apps.
        for (PackageSetting ps : mPackages.values()) {
            if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0 && ps.pkg != null
                    && ps.pkg.preferredActivityFilters != null) {
                ArrayList<PackageParser.ActivityIntentInfo> intents
                        = ps.pkg.preferredActivityFilters;
                for (int i=0; i<intents.size(); i++) {
                    PackageParser.ActivityIntentInfo aii = intents.get(i);
                    applyDefaultPreferredActivityLPw(service, aii, new ComponentName(
                            ps.name, aii.activity.className), userId);
                }
            }
        }

        // Read preferred apps from .../etc/preferred-apps directory.
        File preferredDir = new File(Environment.getRootDirectory(), "etc/preferred-apps");
        if (!preferredDir.exists() || !preferredDir.isDirectory()) {
@@ -1889,21 +1903,8 @@ final class Settings {
        }
    }

    private void readDefaultPreferredActivitiesLPw(PackageManagerService service,
            XmlPullParser parser, int userId)
            throws XmlPullParserException, IOException {
        int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals(TAG_ITEM)) {
                PreferredActivity tmpPa = new PreferredActivity(parser);
                if (tmpPa.mPref.getParseError() == null) {
    private void applyDefaultPreferredActivityLPw(PackageManagerService service,
            IntentFilter tmpPa, ComponentName cn, int userId) {
        // The initial preferences only specify the target activity
        // component and intent-filter, not the set of matches.  So we
        // now need to query for the matches to build the correct
@@ -1912,7 +1913,6 @@ final class Settings {
            Log.d(TAG, "Processing preferred:");
            tmpPa.dump(new LogPrinter(Log.DEBUG, TAG), "  ");
        }
                    final ComponentName cn = tmpPa.mPref.mComponent;
        Intent intent = new Intent();
        int flags = 0;
        intent.setAction(tmpPa.getAction(0));
@@ -1924,28 +1924,83 @@ final class Settings {
                intent.addCategory(cat);
            }
        }
                    if (tmpPa.countDataSchemes() > 0) {

        boolean doNonData = true;

        for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) {
            boolean doScheme = true;
            String scheme = tmpPa.getDataScheme(ischeme);
            for (int issp=0; issp<tmpPa.countDataSchemeSpecificParts(); issp++) {
                Uri.Builder builder = new Uri.Builder();
                        builder.scheme(tmpPa.getDataScheme(0));
                        if (tmpPa.countDataSchemeSpecificParts() > 0) {
                            PatternMatcher path = tmpPa.getDataSchemeSpecificPart(0);
                            builder.opaquePart(path.getPath());
                        } else {
                            if (tmpPa.countDataAuthorities() > 0) {
                                IntentFilter.AuthorityEntry auth = tmpPa.getDataAuthority(0);
                builder.scheme(scheme);
                PatternMatcher ssp = tmpPa.getDataSchemeSpecificPart(issp);
                builder.opaquePart(ssp.getPath());
                Intent finalIntent = new Intent(intent);
                finalIntent.setData(builder.build());
                applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
                        scheme, ssp, null, null, null, userId);
                doScheme = false;
            }
            for (int iauth=0; iauth<tmpPa.countDataAuthorities(); iauth++) {
                boolean doAuth = true;
                IntentFilter.AuthorityEntry auth = tmpPa.getDataAuthority(iauth);
                for (int ipath=0; ipath<tmpPa.countDataPaths(); ipath++) {
                    Uri.Builder builder = new Uri.Builder();
                    builder.scheme(scheme);
                    if (auth.getHost() != null) {
                        builder.authority(auth.getHost());
                    }
                            }
                            if (tmpPa.countDataPaths() > 0) {
                                PatternMatcher path = tmpPa.getDataPath(0);
                    PatternMatcher path = tmpPa.getDataPath(ipath);
                    builder.path(path.getPath());
                    Intent finalIntent = new Intent(intent);
                    finalIntent.setData(builder.build());
                    applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
                            scheme, null, auth, path, null, userId);
                    doAuth = doScheme = false;
                }
                if (doAuth) {
                    Uri.Builder builder = new Uri.Builder();
                    builder.scheme(scheme);
                    if (auth.getHost() != null) {
                        builder.authority(auth.getHost());
                    }
                    Intent finalIntent = new Intent(intent);
                    finalIntent.setData(builder.build());
                    applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
                            scheme, null, auth, null, null, userId);
                    doScheme = false;
                }
            }
            if (doScheme) {
                Uri.Builder builder = new Uri.Builder();
                builder.scheme(scheme);
                Intent finalIntent = new Intent(intent);
                finalIntent.setData(builder.build());
                applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
                        scheme, null, null, null, null, userId);
            }
            doNonData = false;
        }

        for (int idata=0; idata<tmpPa.countDataTypes(); idata++) {
            Intent finalIntent = new Intent(intent);
            String mimeType = tmpPa.getDataType(idata);
            finalIntent.setType(mimeType);
            applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
                    null, null, null, null, mimeType, userId);
            doNonData = false;
        }

        if (doNonData) {
            applyDefaultPreferredActivityLPw(service, intent, flags, cn,
                    null, null, null, null, null, userId);
        }
                        intent.setData(builder.build());
                    } else if (tmpPa.countDataTypes() > 0) {
                        intent.setType(tmpPa.getDataType(0));
    }

    private void applyDefaultPreferredActivityLPw(PackageManagerService service,
            Intent intent, int flags, ComponentName cn, String scheme, PatternMatcher ssp,
            IntentFilter.AuthorityEntry auth, PatternMatcher path, String mimeType,
            int userId) {
        List<ResolveInfo> ri = service.mActivities.queryIntent(intent,
                intent.getType(), flags, 0);
        if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Queried " + intent
@@ -1978,14 +2033,53 @@ final class Settings {
                }
            }
            if (haveAct && !haveNonSys) {
                            PreferredActivity pa = new PreferredActivity(tmpPa, match, set,
                                    tmpPa.mPref.mComponent);
                IntentFilter filter = new IntentFilter();
                if (intent.getAction() != null) {
                    filter.addAction(intent.getAction());
                }
                for (String cat : intent.getCategories()) {
                    filter.addCategory(cat);
                }
                if ((flags&PackageManager.MATCH_DEFAULT_ONLY) != 0) {
                    filter.addCategory(Intent.CATEGORY_DEFAULT);
                }
                if (scheme != null) {
                    filter.addDataScheme(scheme);
                }
                if (ssp != null) {
                    filter.addDataSchemeSpecificPart(ssp.getPath(), ssp.getType());
                }
                if (auth != null) {
                    filter.addDataAuthority(auth);
                }
                if (path != null) {
                    filter.addDataPath(path);
                }
                PreferredActivity pa = new PreferredActivity(filter, match, set, cn);
                editPreferredActivitiesLPw(userId).addFilter(pa);
            } else if (!haveNonSys) {
                            Slog.w(TAG, "No component found for default preferred activity "
                                    + tmpPa.mPref.mComponent);
                Slog.w(TAG, "No component found for default preferred activity " + cn);
            }
        }
    }

    private void readDefaultPreferredActivitiesLPw(PackageManagerService service,
            XmlPullParser parser, int userId)
            throws XmlPullParserException, IOException {
        int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals(TAG_ITEM)) {
                PreferredActivity tmpPa = new PreferredActivity(parser);
                if (tmpPa.mPref.getParseError() == null) {
                    applyDefaultPreferredActivityLPw(service, tmpPa, tmpPa.mPref.mComponent,
                            userId);
                } else {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Error in package manager settings: <preferred-activity> "