• 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.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