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