• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback, Resources sourceRes, int layoutId)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
getFolderElementsMap()55     protected ArrayMap<String, TagParser> getFolderElementsMap() {
56         return getFolderElementsMap(mSourceRes);
57     }
58 
getFolderElementsMap(Resources res)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
getLayoutElementsMap()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
parseContainerAndScreen(XmlResourceParser parser, long[] out)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
invalidPackageOrClass(XmlResourceParser parser)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 
getSingleSystemActivity(List<ResolveInfo> appList)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 
wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList)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 
UriShortcutParser(Resources iconRes)183         public UriShortcutParser(Resources iconRes) {
184             super(iconRes);
185         }
186 
187         @Override
parseIntent(XmlResourceParser parser)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
parseAndAdd(XmlResourceParser parser)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
parseAndAdd(XmlResourceParser parser)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
parseAndAdd(XmlResourceParser parser)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
verifyAndInsert(ComponentName cn, Bundle extras)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