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