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