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

Commit fd2d7aad authored by Mehdi Alizadeh's avatar Mehdi Alizadeh Committed by Android (Google) Code Review
Browse files

Merge changes I1ddcd4e6,I93e306db

* changes:
  Parses share targets from shortcuts.xml
  Use client context to pass ShortcutManager tests
parents 583dfcc3 3277462a
Loading
Loading
Loading
Loading
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright 2018 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.pm;

import android.text.TextUtils;

/**
 * Represents a Share Target definition, read from the application's manifest (shortcuts.xml)
 */
class ShareTargetInfo {
    static class TargetData {
        final String mScheme;
        final String mHost;
        final String mPort;
        final String mPath;
        final String mPathPattern;
        final String mPathPrefix;
        final String mMimeType;

        TargetData(String scheme, String host, String port, String path, String pathPattern,
                String pathPrefix, String mimeType) {
            mScheme = scheme;
            mHost = host;
            mPort = port;
            mPath = path;
            mPathPattern = pathPattern;
            mPathPrefix = pathPrefix;
            mMimeType = mimeType;
        }

        public void toStringInner(StringBuilder strBuilder) {
            if (!TextUtils.isEmpty(mScheme)) {
                strBuilder.append(" scheme=").append(mScheme);
            }
            if (!TextUtils.isEmpty(mHost)) {
                strBuilder.append(" host=").append(mHost);
            }
            if (!TextUtils.isEmpty(mPort)) {
                strBuilder.append(" port=").append(mPort);
            }
            if (!TextUtils.isEmpty(mPath)) {
                strBuilder.append(" path=").append(mPath);
            }
            if (!TextUtils.isEmpty(mPathPattern)) {
                strBuilder.append(" pathPattern=").append(mPathPattern);
            }
            if (!TextUtils.isEmpty(mPathPrefix)) {
                strBuilder.append(" pathPrefix=").append(mPathPrefix);
            }
            if (!TextUtils.isEmpty(mMimeType)) {
                strBuilder.append(" mimeType=").append(mMimeType);
            }
        }

        @Override
        public String toString() {
            StringBuilder strBuilder = new StringBuilder();
            toStringInner(strBuilder);
            return strBuilder.toString();
        }
    }

    final TargetData[] mTargetData;
    final String mTargetClass;
    final String[] mCategories;

    ShareTargetInfo(TargetData[] data, String targetClass, String[] categories) {
        mTargetData = data;
        mTargetClass = targetClass;
        mCategories = categories;
    }

    @Override
    public String toString() {
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append("targetClass=").append(mTargetClass);
        for (int i = 0; i < mTargetData.length; i++) {
            strBuilder.append(" data={");
            mTargetData[i].toStringInner(strBuilder);
            strBuilder.append("}");
        }
        for (int i = 0; i < mCategories.length; i++) {
            strBuilder.append(" category=").append(mCategories[i]);
        }

        return strBuilder.toString();
    }
}
+14 −3
Original line number Diff line number Diff line
@@ -110,6 +110,11 @@ class ShortcutPackage extends ShortcutPackageItem {
     */
    final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();

    /**
     * All the share targets from the package
     */
    private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);

    /**
     * # of times the package has called rate-limited APIs.
     */
@@ -739,15 +744,16 @@ class ShortcutPackage extends ShortcutPackageItem {
        List<ShortcutInfo> newManifestShortcutList = null;
        try {
            newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
                    getPackageName(), getPackageUserId());
                    getPackageName(), getPackageUserId(), mShareTargets);
        } catch (IOException|XmlPullParserException e) {
            Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
        }
        final int manifestShortcutSize = newManifestShortcutList == null ? 0
                : newManifestShortcutList.size();
        if (ShortcutService.DEBUG) {
            Slog.d(TAG, String.format("Package %s has %d manifest shortcut(s)",
                    getPackageName(), manifestShortcutSize));
            Slog.d(TAG,
                    String.format("Package %s has %d manifest shortcut(s), and %d share target(s)",
                            getPackageName(), manifestShortcutSize, mShareTargets.size()));
        }
        if (isNewApp && (manifestShortcutSize == 0)) {
            // If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
@@ -1657,6 +1663,11 @@ class ShortcutPackage extends ShortcutPackageItem {
        return new ArrayList<>(mShortcuts.values());
    }

    @VisibleForTesting
    List<ShareTargetInfo> getAllShareTargetsForTest() {
        return new ArrayList<>(mShareTargets);
    }

    @Override
    public void verifyStates() {
        super.verifyStates();
+140 −5
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.server.pm;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
@@ -55,10 +56,14 @@ public class ShortcutParser {
    private static final String TAG_SHORTCUT = "shortcut";
    private static final String TAG_INTENT = "intent";
    private static final String TAG_CATEGORIES = "categories";
    private static final String TAG_SHARE_TARGET = "share-target";
    private static final String TAG_DATA = "data";
    private static final String TAG_CATEGORY = "category";

    @Nullable
    public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
            String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
    public static List<ShortcutInfo> parseShortcuts(ShortcutService service, String packageName,
            @UserIdInt int userId, @NonNull List<ShareTargetInfo> outShareTargets)
            throws IOException, XmlPullParserException {
        if (ShortcutService.DEBUG) {
            Slog.d(TAG, String.format("Scanning package %s for manifest shortcuts on user %d",
                    packageName, userId));
@@ -69,6 +74,7 @@ public class ShortcutParser {
        }

        List<ShortcutInfo> result = null;
        outShareTargets.clear();

        try {
            final int size = activities.size();
@@ -82,8 +88,8 @@ public class ShortcutParser {
                        service.getActivityInfoWithMetadata(
                        activityInfoNoMetadata.getComponentName(), userId);
                if (activityInfoWithMetadata != null) {
                    result = parseShortcutsOneFile(
                            service, activityInfoWithMetadata, packageName, userId, result);
                    result = parseShortcutsOneFile(service, activityInfoWithMetadata, packageName,
                            userId, result, outShareTargets);
                }
            }
        } catch (RuntimeException e) {
@@ -99,7 +105,8 @@ public class ShortcutParser {
    private static List<ShortcutInfo> parseShortcutsOneFile(
            ShortcutService service,
            ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
            List<ShortcutInfo> result) throws IOException, XmlPullParserException {
            List<ShortcutInfo> result, @NonNull List<ShareTargetInfo> outShareTargets)
            throws IOException, XmlPullParserException {
        if (ShortcutService.DEBUG) {
            Slog.d(TAG, String.format(
                    "Checking main activity %s", activityInfo.getComponentName()));
@@ -126,9 +133,19 @@ public class ShortcutParser {
            // after parsing <intent>.  We keep the current one in here.
            ShortcutInfo currentShortcut = null;

            // We instantiate ShareTargetInfo at <share-target>, but add it to outShareTargets at
            // </share-target>, after parsing <data> and <category>. We keep the current one here.
            ShareTargetInfo currentShareTarget = null;

            // Keeps parsed categories for both ShortcutInfo and ShareTargetInfo
            Set<String> categories = null;

            // Keeps parsed intents for ShortcutInfo
            final ArrayList<Intent> intents = new ArrayList<>();

            // Keeps parsed data fields for ShareTargetInfo
            final ArrayList<ShareTargetInfo.TargetData> dataList = new ArrayList<>();

            outer:
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) {
@@ -194,6 +211,32 @@ public class ShortcutParser {
                    continue;
                }

                // When a share-target tag is closing, publish.
                if ((type == XmlPullParser.END_TAG) && (depth == 2)
                        && (TAG_SHARE_TARGET.equals(tag))) {
                    if (currentShareTarget == null) {
                        // ShareTarget was invalid.
                        continue;
                    }
                    final ShareTargetInfo sti = currentShareTarget;
                    currentShareTarget = null; // Make sure to null out for the next iteration.

                    if (categories == null || categories.isEmpty() || dataList.isEmpty()) {
                        // Incomplete ShareTargetInfo.
                        continue;
                    }

                    final ShareTargetInfo newShareTarget = new ShareTargetInfo(
                            dataList.toArray(new ShareTargetInfo.TargetData[dataList.size()]),
                            sti.mTargetClass, categories.toArray(new String[categories.size()]));
                    outShareTargets.add(newShareTarget);
                    if (ShortcutService.DEBUG) {
                        Slog.d(TAG, "ShareTarget added: " + newShareTarget.toString());
                    }
                    categories = null;
                    dataList.clear();
                }

                // Otherwise, just look at start tags.
                if (type != XmlPullParser.START_TAG) {
                    continue;
@@ -224,6 +267,17 @@ public class ShortcutParser {
                    categories = null;
                    continue;
                }
                if (depth == 2 && TAG_SHARE_TARGET.equals(tag)) {
                    final ShareTargetInfo sti = parseShareTargetAttributes(service, attrs);
                    if (sti == null) {
                        // ShareTarget was invalid.
                        continue;
                    }
                    currentShareTarget = sti;
                    categories = null;
                    dataList.clear();
                    continue;
                }
                if (depth == 3 && TAG_INTENT.equals(tag)) {
                    if ((currentShortcut == null)
                            || !currentShortcut.isEnabled()) {
@@ -258,6 +312,34 @@ public class ShortcutParser {
                    categories.add(name);
                    continue;
                }
                if (depth == 3 && TAG_CATEGORY.equals(tag)) {
                    if ((currentShareTarget == null)) {
                        continue;
                    }
                    final String name = parseCategory(service, attrs);
                    if (TextUtils.isEmpty(name)) {
                        Log.e(TAG, "Empty category found. activity=" + activity);
                        continue;
                    }

                    if (categories == null) {
                        categories = new ArraySet<>();
                    }
                    categories.add(name);
                    continue;
                }
                if (depth == 3 && TAG_DATA.equals(tag)) {
                    if ((currentShareTarget == null)) {
                        continue;
                    }
                    final ShareTargetInfo.TargetData data = parseShareTargetData(service, attrs);
                    if (data == null) {
                        Log.e(TAG, "Invalid data tag found. activity=" + activity);
                        continue;
                    }
                    dataList.add(data);
                    continue;
                }

                Log.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
            }
@@ -369,4 +451,57 @@ public class ShortcutParser {
                null, // bitmap path
                disabledReason);
    }

    private static String parseCategory(ShortcutService service, AttributeSet attrs) {
        final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
                R.styleable.IntentCategory);
        try {
            if (sa.getType(R.styleable.IntentCategory_name) != TypedValue.TYPE_STRING) {
                Log.w(TAG, "android:name must be string literal.");
                return null;
            }
            return sa.getString(R.styleable.IntentCategory_name);
        } finally {
            sa.recycle();
        }
    }

    private static ShareTargetInfo parseShareTargetAttributes(ShortcutService service,
            AttributeSet attrs) {
        final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
                R.styleable.Intent);
        try {
            String targetClass = sa.getString(R.styleable.Intent_targetClass);
            if (TextUtils.isEmpty(targetClass)) {
                Log.w(TAG, "android:targetClass must be provided.");
                return null;
            }
            return new ShareTargetInfo(null, targetClass, null);
        } finally {
            sa.recycle();
        }
    }

    private static ShareTargetInfo.TargetData parseShareTargetData(ShortcutService service,
            AttributeSet attrs) {
        final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
                R.styleable.AndroidManifestData);
        try {
            if (sa.getType(R.styleable.AndroidManifestData_mimeType) != TypedValue.TYPE_STRING) {
                Log.w(TAG, "android:mimeType must be string literal.");
                return null;
            }
            String scheme = sa.getString(R.styleable.AndroidManifestData_scheme);
            String host = sa.getString(R.styleable.AndroidManifestData_host);
            String port = sa.getString(R.styleable.AndroidManifestData_port);
            String path = sa.getString(R.styleable.AndroidManifestData_path);
            String pathPattern = sa.getString(R.styleable.AndroidManifestData_pathPattern);
            String pathPrefix = sa.getString(R.styleable.AndroidManifestData_pathPrefix);
            String mimeType = sa.getString(R.styleable.AndroidManifestData_mimeType);
            return new ShareTargetInfo.TargetData(scheme, host, port, path, pathPattern, pathPrefix,
                    mimeType);
        } finally {
            sa.recycle();
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -41,8 +41,8 @@ import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -98,8 +98,8 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.internal.util.StatLogger;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.ShortcutUser.PackageWithUser;

+87 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2018 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.
  -->

<!-- Test XML resource to read share-targets from, used in ShortcutManagerTest1.java -->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
        android:shortcutId="dummy_shortcut1"
        android:enabled="true"
        android:shortcutShortLabel="@string/shortcut_title1">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.test.somepackage"
            android:targetClass="com.test.somepackage.someclass" />
        <categories android:name="android.shortcut.conversation" />
    </shortcut>

    <!-- Valid share target definition -->
    <share-target android:targetClass="com.test.directshare.TestActivity1">
        <data
            android:scheme="http"
            android:host="www.google.com"
            android:port="1234"
            android:path="somePath"
            android:pathPrefix="somePathPrefix"
            android:pathPattern="somePathPattern"
            android:mimeType="text/plain"/>
        <category android:name="com.test.category.CATEGORY1"/>
        <category android:name="com.test.category.CATEGORY2"/>
    </share-target>

    <!-- Share target missing data tag, will be dropped -->
    <share-target android:targetClass="com.test.directshare.TestActivity">
        <category android:name="com.test.category.CATEGORY2"/>
    </share-target>

    <!-- Share target missing target class, will be dropped -->
    <share-target>
        <data
            android:scheme="file"
            android:host="www.somehost.com"
            android:port="1234"
            android:mimeType="video/*"/>
        <category android:name="com.test.category.CATEGORY3"/>
    </share-target>

    <shortcut
        android:shortcutId="dummy_shortcut2"
        android:enabled="true"
        android:shortcutShortLabel="@string/shortcut_title1">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.test.somepackage"
            android:targetClass="com.test.somepackage.someclass" />
        <categories android:name="android.shortcut.conversation" />
    </shortcut>

    <!-- Share target missing category, will be dropped -->
    <share-target android:targetClass="com.test.directshare.TestActivity">
        <data
            android:scheme="content"
            android:mimeType="text/plain"/>
    </share-target>

    <!-- Valid share target definition -->
    <share-target android:targetClass="com.test.directshare.TestActivity5">
        <category android:name="com.test.category.CATEGORY5"/>
        <category android:name="com.test.category.CATEGORY6"/>
        <data android:mimeType="video/mp4"/>
        <data
            android:scheme="content"
            android:mimeType="video/*"/>
    </share-target>
</shortcuts>
 No newline at end of file
Loading