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