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