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