Loading services/core/java/com/android/server/pm/ShareTargetInfo.java 0 → 100644 +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(); } } services/core/java/com/android/server/pm/ShortcutPackage.java +14 −3 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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. Loading Loading @@ -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(); Loading services/core/java/com/android/server/pm/ShortcutParser.java +140 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)); Loading @@ -69,6 +74,7 @@ public class ShortcutParser { } List<ShortcutInfo> result = null; outShareTargets.clear(); try { final int size = activities.size(); Loading @@ -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) { Loading @@ -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())); Loading @@ -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)) { Loading Loading @@ -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; Loading Loading @@ -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()) { Loading Loading @@ -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)); } Loading Loading @@ -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(); } } } services/core/java/com/android/server/pm/ShortcutService.java +3 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading services/tests/servicestests/res/xml/shortcut_share_targets.xml 0 → 100644 +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
services/core/java/com/android/server/pm/ShareTargetInfo.java 0 → 100644 +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(); } }
services/core/java/com/android/server/pm/ShortcutPackage.java +14 −3 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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. Loading Loading @@ -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(); Loading
services/core/java/com/android/server/pm/ShortcutParser.java +140 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)); Loading @@ -69,6 +74,7 @@ public class ShortcutParser { } List<ShortcutInfo> result = null; outShareTargets.clear(); try { final int size = activities.size(); Loading @@ -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) { Loading @@ -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())); Loading @@ -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)) { Loading Loading @@ -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; Loading Loading @@ -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()) { Loading Loading @@ -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)); } Loading Loading @@ -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(); } } }
services/core/java/com/android/server/pm/ShortcutService.java +3 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading
services/tests/servicestests/res/xml/shortcut_share_targets.xml 0 → 100644 +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