ShortcutParser.java revision 22fcc68e6be0edaa98f3dacf79d580a5e5d50005
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.server.pm; 17 18import android.annotation.Nullable; 19import android.annotation.UserIdInt; 20import android.content.ComponentName; 21import android.content.Intent; 22import android.content.pm.ActivityInfo; 23import android.content.pm.PackageInfo; 24import android.content.pm.ShortcutInfo; 25import android.content.res.TypedArray; 26import android.content.res.XmlResourceParser; 27import android.net.Uri; 28import android.text.TextUtils; 29import android.util.ArraySet; 30import android.util.AttributeSet; 31import android.util.Slog; 32import android.util.Xml; 33 34import com.android.internal.R; 35import com.android.internal.annotations.VisibleForTesting; 36 37import org.xmlpull.v1.XmlPullParser; 38import org.xmlpull.v1.XmlPullParserException; 39 40import java.io.IOException; 41import java.util.ArrayList; 42import java.util.List; 43import java.util.Set; 44 45public class ShortcutParser { 46 private static final String TAG = ShortcutService.TAG; 47 48 private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE 49 50 @VisibleForTesting 51 static final String METADATA_KEY = "android.pm.Shortcuts"; 52 53 private static final String TAG_SHORTCUTS = "shortcuts"; 54 private static final String TAG_SHORTCUT = "shortcut"; 55 56 @Nullable 57 public static List<ShortcutInfo> parseShortcuts(ShortcutService service, 58 String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException { 59 final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId); 60 61 List<ShortcutInfo> result = null; 62 63 if (pi != null && pi.activities != null) { 64 for (ActivityInfo activityInfo : pi.activities) { 65 result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result); 66 } 67 } 68 return result; 69 } 70 71 private static List<ShortcutInfo> parseShortcutsOneFile( 72 ShortcutService service, 73 ActivityInfo activityInfo, String packageName, @UserIdInt int userId, 74 List<ShortcutInfo> result) throws IOException, XmlPullParserException { 75 XmlResourceParser parser = null; 76 try { 77 parser = service.injectXmlMetaData(activityInfo, METADATA_KEY); 78 if (parser == null) { 79 return result; 80 } 81 82 final ComponentName activity = new ComponentName(packageName, activityInfo.name); 83 84 final AttributeSet attrs = Xml.asAttributeSet(parser); 85 86 int type; 87 88 outer: 89 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 90 && (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) { 91 if (type != XmlPullParser.START_TAG) { 92 continue; 93 } 94 final int depth = parser.getDepth(); 95 final String tag = parser.getName(); 96 97 if (depth == 1 && TAG_SHORTCUTS.equals(tag)) { 98 continue; // Root tag. 99 } 100 if (depth == 2 && TAG_SHORTCUT.equals(tag)) { 101 final ShortcutInfo si = parseShortcutAttributes( 102 service, attrs, packageName, activity, userId); 103 if (ShortcutService.DEBUG) { 104 Slog.d(TAG, "Shortcut=" + si); 105 } 106 if (result != null) { 107 for (int i = result.size() - 1; i >= 0; i--) { 108 if (si.getId().equals(result.get(i).getId())) { 109 Slog.w(TAG, "Duplicate shortcut ID detected, skipping."); 110 continue outer; 111 } 112 } 113 } 114 115 if (si != null) { 116 if (result == null) { 117 result = new ArrayList<>(); 118 } 119 result.add(si); 120 } 121 continue; 122 } 123 Slog.w(TAG, "Unknown tag " + tag); 124 } 125 } finally { 126 if (parser != null) { 127 parser.close(); 128 } 129 } 130 return result; 131 } 132 133 private static ShortcutInfo parseShortcutAttributes(ShortcutService service, 134 AttributeSet attrs, String packageName, ComponentName activity, 135 @UserIdInt int userId) { 136 final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs, 137 R.styleable.Shortcut); 138 try { 139 final String id = sa.getString(R.styleable.Shortcut_shortcutId); 140 final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true); 141 final int rank = sa.getInt(R.styleable.Shortcut_shortcutRank, 0); 142 final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0); 143 final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutTitle, 0); 144 final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutText, 0); 145 final int disabledMessageResId = sa.getResourceId( 146 R.styleable.Shortcut_shortcutDisabledMessage, 0); 147 final String categories = sa.getString(R.styleable.Shortcut_shortcutCategories); 148 String intentAction = sa.getString(R.styleable.Shortcut_shortcutIntentAction); 149 final String intentData = sa.getString(R.styleable.Shortcut_shortcutIntentData); 150 151 if (TextUtils.isEmpty(id)) { 152 Slog.w(TAG, "Shortcut ID must be provided. activity=" + activity); 153 return null; 154 } 155 if (titleResId == 0) { 156 Slog.w(TAG, "Shortcut title must be provided. activity=" + activity); 157 return null; 158 } 159 if (TextUtils.isEmpty(intentAction)) { 160 if (enabled) { 161 Slog.w(TAG, "Shortcut intent action must be provided. activity=" + activity); 162 return null; 163 } else { 164 // Disabled shortcut doesn't have to have an action, but just set VIEW as the 165 // default. 166 intentAction = Intent.ACTION_VIEW; 167 } 168 } 169 170 final ArraySet<String> categoriesSet; 171 if (categories == null) { 172 categoriesSet = null; 173 } else { 174 final String[] arr = categories.split(":"); 175 categoriesSet = new ArraySet<>(arr.length); 176 for (String v : arr) { 177 categoriesSet.add(v); 178 } 179 } 180 final Intent intent = new Intent(intentAction); 181 if (!TextUtils.isEmpty(intentData)) { 182 intent.setData(Uri.parse(intentData)); 183 } 184 185 return createShortcutFromManifest( 186 service, 187 userId, 188 id, 189 packageName, 190 activity, 191 titleResId, 192 textResId, 193 disabledMessageResId, 194 categoriesSet, 195 intent, 196 rank, 197 iconResId, 198 enabled); 199 } finally { 200 sa.recycle(); 201 } 202 } 203 204 private static ShortcutInfo createShortcutFromManifest(ShortcutService service, 205 @UserIdInt int userId, String id, String packageName, ComponentName activityComponent, 206 int titleResId, int textResId, int disabledMessageResId, Set<String> categories, 207 Intent intent, int rank, int iconResId, boolean enabled) { 208 209 final int flags = 210 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED) 211 | ShortcutInfo.FLAG_IMMUTABLE 212 | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0); 213 214 return new ShortcutInfo( 215 userId, 216 id, 217 packageName, 218 activityComponent, 219 null, // icon 220 null, // title string 221 titleResId, 222 null, // text string 223 textResId, 224 null, // disabled message string 225 disabledMessageResId, 226 categories, 227 intent, 228 null, // intent extras 229 rank, 230 null, // extras 231 service.injectCurrentTimeMillis(), 232 flags, 233 iconResId, 234 null); // bitmap path 235 } 236} 237