1 package com.android.launcher3; 2 3 import android.appwidget.AppWidgetHost; 4 import android.appwidget.AppWidgetManager; 5 import android.content.ComponentName; 6 import android.content.Context; 7 import android.content.Intent; 8 import android.content.pm.ActivityInfo; 9 import android.content.pm.ApplicationInfo; 10 import android.content.pm.PackageManager; 11 import android.content.pm.ResolveInfo; 12 import android.content.res.Resources; 13 import android.os.Bundle; 14 import android.text.TextUtils; 15 import android.util.ArrayMap; 16 import android.util.Log; 17 import com.android.launcher3.LauncherSettings.Favorites; 18 import com.android.launcher3.util.Thunk; 19 import java.io.IOException; 20 import java.net.URISyntaxException; 21 import java.util.List; 22 import org.xmlpull.v1.XmlPullParser; 23 import org.xmlpull.v1.XmlPullParserException; 24 25 /** 26 * Implements the layout parser with rules for internal layouts and partner layouts. 27 */ 28 public class DefaultLayoutParser extends AutoInstallsLayout { 29 private static final String TAG = "DefaultLayoutParser"; 30 31 protected static final String TAG_RESOLVE = "resolve"; 32 private static final String TAG_FAVORITES = "favorites"; 33 protected static final String TAG_FAVORITE = "favorite"; 34 private static final String TAG_APPWIDGET = "appwidget"; 35 protected static final String TAG_SHORTCUT = "shortcut"; 36 private static final String TAG_FOLDER = "folder"; 37 private static final String TAG_PARTNER_FOLDER = "partner-folder"; 38 39 protected static final String ATTR_URI = "uri"; 40 private static final String ATTR_CONTAINER = "container"; 41 private static final String ATTR_SCREEN = "screen"; 42 private static final String ATTR_FOLDER_ITEMS = "folderItems"; 43 44 // TODO: Remove support for this broadcast, instead use widget options to send bind time options 45 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = 46 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; 47 DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback, Resources sourceRes, int layoutId)48 public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost, 49 LayoutParserCallback callback, Resources sourceRes, int layoutId) { 50 super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES); 51 } 52 53 @Override getFolderElementsMap()54 protected ArrayMap<String, TagParser> getFolderElementsMap() { 55 return getFolderElementsMap(mSourceRes); 56 } 57 getFolderElementsMap(Resources res)58 @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) { 59 ArrayMap<String, TagParser> parsers = new ArrayMap<>(); 60 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser()); 61 parsers.put(TAG_SHORTCUT, new UriShortcutParser(res)); 62 return parsers; 63 } 64 65 @Override getLayoutElementsMap()66 protected ArrayMap<String, TagParser> getLayoutElementsMap() { 67 ArrayMap<String, TagParser> parsers = new ArrayMap<>(); 68 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser()); 69 parsers.put(TAG_APPWIDGET, new AppWidgetParser()); 70 parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes)); 71 parsers.put(TAG_RESOLVE, new ResolveParser()); 72 parsers.put(TAG_FOLDER, new MyFolderParser()); 73 parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser()); 74 return parsers; 75 } 76 77 @Override parseContainerAndScreen(XmlPullParser parser, int[] out)78 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) { 79 out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP; 80 String strContainer = getAttributeValue(parser, ATTR_CONTAINER); 81 if (strContainer != null) { 82 out[0] = Integer.parseInt(strContainer); 83 } 84 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN)); 85 } 86 87 /** 88 * AppShortcutParser which also supports adding URI based intents 89 */ 90 public class AppShortcutWithUriParser extends AppShortcutParser { 91 92 @Override invalidPackageOrClass(XmlPullParser parser)93 protected int invalidPackageOrClass(XmlPullParser parser) { 94 final String uri = getAttributeValue(parser, ATTR_URI); 95 if (TextUtils.isEmpty(uri)) { 96 Log.e(TAG, "Skipping invalid <favorite> with no component or uri"); 97 return -1; 98 } 99 100 final Intent metaIntent; 101 try { 102 metaIntent = Intent.parseUri(uri, 0); 103 } catch (URISyntaxException e) { 104 Log.e(TAG, "Unable to add meta-favorite: " + uri, e); 105 return -1; 106 } 107 108 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent, 109 PackageManager.MATCH_DEFAULT_ONLY); 110 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities( 111 metaIntent, PackageManager.MATCH_DEFAULT_ONLY); 112 113 // Verify that the result is an app and not just the resolver dialog asking which 114 // app to use. 115 if (wouldLaunchResolverActivity(resolved, appList)) { 116 // If only one of the results is a system app then choose that as the default. 117 final ResolveInfo systemApp = getSingleSystemActivity(appList); 118 if (systemApp == null) { 119 // There is no logical choice for this meta-favorite, so rather than making 120 // a bad choice just add nothing. 121 Log.w(TAG, "No preference or single system activity found for " 122 + metaIntent.toString()); 123 return -1; 124 } 125 resolved = systemApp; 126 } 127 final ActivityInfo info = resolved.activityInfo; 128 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName); 129 if (intent == null) { 130 return -1; 131 } 132 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 133 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 134 135 return addShortcut(info.loadLabel(mPackageManager).toString(), intent, 136 Favorites.ITEM_TYPE_APPLICATION); 137 } 138 getSingleSystemActivity(List<ResolveInfo> appList)139 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) { 140 ResolveInfo systemResolve = null; 141 final int N = appList.size(); 142 for (int i = 0; i < N; ++i) { 143 try { 144 ApplicationInfo info = mPackageManager.getApplicationInfo( 145 appList.get(i).activityInfo.packageName, 0); 146 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 147 if (systemResolve != null) { 148 return null; 149 } else { 150 systemResolve = appList.get(i); 151 } 152 } 153 } catch (PackageManager.NameNotFoundException e) { 154 Log.w(TAG, "Unable to get info about resolve results", e); 155 return null; 156 } 157 } 158 return systemResolve; 159 } 160 wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList)161 private boolean wouldLaunchResolverActivity(ResolveInfo resolved, 162 List<ResolveInfo> appList) { 163 // If the list contains the above resolved activity, then it can't be 164 // ResolverActivity itself. 165 for (int i = 0; i < appList.size(); ++i) { 166 ResolveInfo tmp = appList.get(i); 167 if (tmp.activityInfo.name.equals(resolved.activityInfo.name) 168 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) { 169 return false; 170 } 171 } 172 return true; 173 } 174 } 175 176 177 /** 178 * Shortcut parser which allows any uri and not just web urls. 179 */ 180 public class UriShortcutParser extends ShortcutParser { 181 UriShortcutParser(Resources iconRes)182 public UriShortcutParser(Resources iconRes) { 183 super(iconRes); 184 } 185 186 @Override parseIntent(XmlPullParser parser)187 protected Intent parseIntent(XmlPullParser parser) { 188 String uri = null; 189 try { 190 uri = getAttributeValue(parser, ATTR_URI); 191 return Intent.parseUri(uri, 0); 192 } catch (URISyntaxException e) { 193 Log.w(TAG, "Shortcut has malformed uri: " + uri); 194 return null; // Oh well 195 } 196 } 197 } 198 199 /** 200 * Contains a list of <favorite> nodes, and accepts the first successfully parsed node. 201 */ 202 public class ResolveParser implements TagParser { 203 204 private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser(); 205 206 @Override parseAndAdd(XmlPullParser parser)207 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, 208 IOException { 209 final int groupDepth = parser.getDepth(); 210 int type; 211 int addedId = -1; 212 while ((type = parser.next()) != XmlPullParser.END_TAG || 213 parser.getDepth() > groupDepth) { 214 if (type != XmlPullParser.START_TAG || addedId > -1) { 215 continue; 216 } 217 final String fallback_item_name = parser.getName(); 218 if (TAG_FAVORITE.equals(fallback_item_name)) { 219 addedId = mChildParser.parseAndAdd(parser); 220 } else { 221 Log.e(TAG, "Fallback groups can contain only favorites, found " 222 + fallback_item_name); 223 } 224 } 225 return addedId; 226 } 227 } 228 229 /** 230 * A parser which adds a folder whose contents come from partner apk. 231 */ 232 @Thunk class PartnerFolderParser implements TagParser { 233 234 @Override parseAndAdd(XmlPullParser parser)235 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, 236 IOException { 237 // Folder contents come from an external XML resource 238 final Partner partner = Partner.get(mPackageManager); 239 if (partner != null) { 240 final Resources partnerRes = partner.getResources(); 241 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER, 242 "xml", partner.getPackageName()); 243 if (resId != 0) { 244 final XmlPullParser partnerParser = partnerRes.getXml(resId); 245 beginDocument(partnerParser, TAG_FOLDER); 246 247 FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes)); 248 return folderParser.parseAndAdd(partnerParser); 249 } 250 } 251 return -1; 252 } 253 } 254 255 /** 256 * An extension of FolderParser which allows adding items from a different xml. 257 */ 258 @Thunk class MyFolderParser extends FolderParser { 259 260 @Override parseAndAdd(XmlPullParser parser)261 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, 262 IOException { 263 final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0); 264 if (resId != 0) { 265 parser = mSourceRes.getXml(resId); 266 beginDocument(parser, TAG_FOLDER); 267 } 268 return super.parseAndAdd(parser); 269 } 270 } 271 272 273 /** 274 * AppWidget parser which enforces that the app is already installed when the layout is parsed. 275 */ 276 protected class AppWidgetParser extends PendingWidgetParser { 277 278 @Override verifyAndInsert(ComponentName cn, Bundle extras)279 protected int verifyAndInsert(ComponentName cn, Bundle extras) { 280 try { 281 mPackageManager.getReceiverInfo(cn, 0); 282 } catch (Exception e) { 283 String[] packages = mPackageManager.currentToCanonicalPackageNames( 284 new String[] { cn.getPackageName() }); 285 cn = new ComponentName(packages[0], cn.getClassName()); 286 try { 287 mPackageManager.getReceiverInfo(cn, 0); 288 } catch (Exception e1) { 289 Log.d(TAG, "Can't find widget provider: " + cn.getClassName()); 290 return -1; 291 } 292 } 293 294 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); 295 int insertedId = -1; 296 try { 297 int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); 298 299 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) { 300 Log.e(TAG, "Unable to bind app widget id " + cn); 301 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 302 return -1; 303 } 304 305 mValues.put(Favorites.APPWIDGET_ID, appWidgetId); 306 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); 307 mValues.put(Favorites._ID, mCallback.generateNewItemId()); 308 insertedId = mCallback.insertAndCheck(mDb, mValues); 309 if (insertedId < 0) { 310 mAppWidgetHost.deleteAppWidgetId(appWidgetId); 311 return insertedId; 312 } 313 314 // Send a broadcast to configure the widget 315 if (!extras.isEmpty()) { 316 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); 317 intent.setComponent(cn); 318 intent.putExtras(extras); 319 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 320 mContext.sendBroadcast(intent); 321 } 322 } catch (RuntimeException ex) { 323 Log.e(TAG, "Problem allocating appWidgetId", ex); 324 } 325 return insertedId; 326 } 327 } 328 } 329