ShortcutParser.java revision 7001a6154088c87a31d56641762ff0c2a48f1d57
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 int rank = 0; 89 final int maxShortcuts = service.getMaxActivityShortcuts(); 90 int numShortcuts = 0; 91 92 outer: 93 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 94 && (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) { 95 if (type != XmlPullParser.START_TAG) { 96 continue; 97 } 98 final int depth = parser.getDepth(); 99 final String tag = parser.getName(); 100 101 if (depth == 1 && TAG_SHORTCUTS.equals(tag)) { 102 continue; // Root tag. 103 } 104 if (depth == 2 && TAG_SHORTCUT.equals(tag)) { 105 final ShortcutInfo si = parseShortcutAttributes( 106 service, attrs, packageName, activity, userId, rank++); 107 if (ShortcutService.DEBUG) { 108 Slog.d(TAG, "Shortcut=" + si); 109 } 110 if (result != null) { 111 for (int i = result.size() - 1; i >= 0; i--) { 112 if (si.getId().equals(result.get(i).getId())) { 113 Slog.w(TAG, "Duplicate shortcut ID detected, skipping."); 114 continue outer; 115 } 116 } 117 } 118 119 if (si != null) { 120 if (numShortcuts >= maxShortcuts) { 121 Slog.w(TAG, "More than " + maxShortcuts + " shortcuts found for " 122 + activityInfo.getComponentName() + ", ignoring the rest."); 123 return result; 124 } 125 126 if (result == null) { 127 result = new ArrayList<>(); 128 } 129 result.add(si); 130 numShortcuts++; 131 } 132 continue; 133 } 134 Slog.w(TAG, "Unknown tag " + tag); 135 } 136 } finally { 137 if (parser != null) { 138 parser.close(); 139 } 140 } 141 return result; 142 } 143 144 private static ShortcutInfo parseShortcutAttributes(ShortcutService service, 145 AttributeSet attrs, String packageName, ComponentName activity, 146 @UserIdInt int userId, int rank) { 147 final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs, 148 R.styleable.Shortcut); 149 try { 150 final String id = sa.getString(R.styleable.Shortcut_shortcutId); 151 final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true); 152 final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0); 153 final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0); 154 final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0); 155 final int disabledMessageResId = sa.getResourceId( 156 R.styleable.Shortcut_shortcutDisabledMessage, 0); 157 final String categories = sa.getString(R.styleable.Shortcut_shortcutCategories); 158 String intentAction = sa.getString(R.styleable.Shortcut_shortcutIntentAction); 159 final String intentData = sa.getString(R.styleable.Shortcut_shortcutIntentData); 160 161 if (TextUtils.isEmpty(id)) { 162 Slog.w(TAG, "Shortcut ID must be provided. activity=" + activity); 163 return null; 164 } 165 if (titleResId == 0) { 166 Slog.w(TAG, "Shortcut title must be provided. activity=" + activity); 167 return null; 168 } 169 if (TextUtils.isEmpty(intentAction)) { 170 if (enabled) { 171 Slog.w(TAG, "Shortcut intent action must be provided. activity=" + activity); 172 return null; 173 } else { 174 // Disabled shortcut doesn't have to have an action, but just set VIEW as the 175 // default. 176 intentAction = Intent.ACTION_VIEW; 177 } 178 } 179 180 final ArraySet<String> categoriesSet; 181 if (categories == null) { 182 categoriesSet = null; 183 } else { 184 final String[] arr = categories.split(":"); 185 categoriesSet = new ArraySet<>(arr.length); 186 for (String v : arr) { 187 categoriesSet.add(v); 188 } 189 } 190 final Intent intent = new Intent(intentAction); 191 if (!TextUtils.isEmpty(intentData)) { 192 intent.setData(Uri.parse(intentData)); 193 } 194 195 return createShortcutFromManifest( 196 service, 197 userId, 198 id, 199 packageName, 200 activity, 201 titleResId, 202 textResId, 203 disabledMessageResId, 204 categoriesSet, 205 intent, 206 rank, 207 iconResId, 208 enabled); 209 } finally { 210 sa.recycle(); 211 } 212 } 213 214 private static ShortcutInfo createShortcutFromManifest(ShortcutService service, 215 @UserIdInt int userId, String id, String packageName, ComponentName activityComponent, 216 int titleResId, int textResId, int disabledMessageResId, Set<String> categories, 217 Intent intent, int rank, int iconResId, boolean enabled) { 218 219 final int flags = 220 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED) 221 | ShortcutInfo.FLAG_IMMUTABLE 222 | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0); 223 224 return new ShortcutInfo( 225 userId, 226 id, 227 packageName, 228 activityComponent, 229 null, // icon 230 null, // title string 231 titleResId, 232 null, // text string 233 textResId, 234 null, // disabled message string 235 disabledMessageResId, 236 categories, 237 intent, 238 null, // intent extras 239 rank, 240 null, // extras 241 service.injectCurrentTimeMillis(), 242 flags, 243 iconResId, 244 null); // bitmap path 245 } 246} 247