1 package com.android.launcher3;
2 
3 import android.appwidget.AppWidgetManager;
4 import android.content.ComponentName;
5 import android.content.Context;
6 import android.content.Intent;
7 import android.content.pm.ActivityInfo;
8 import android.content.pm.ApplicationInfo;
9 import android.content.pm.PackageManager;
10 import android.content.pm.ResolveInfo;
11 import android.content.res.Resources;
12 import android.os.Bundle;
13 import android.text.TextUtils;
14 import android.util.ArrayMap;
15 import android.util.Log;
16 
17 import com.android.launcher3.LauncherSettings.Favorites;
18 import com.android.launcher3.util.Partner;
19 import com.android.launcher3.util.Thunk;
20 import com.android.launcher3.widget.LauncherWidgetHolder;
21 
22 import org.xmlpull.v1.XmlPullParser;
23 import org.xmlpull.v1.XmlPullParserException;
24 
25 import java.io.IOException;
26 import java.net.URISyntaxException;
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     public static final String RES_PARTNER_FOLDER = "partner_folder";
49     public static final String RES_PARTNER_DEFAULT_LAYOUT = "partner_default_layout";
50 
51     // TODO: Remove support for this broadcast, instead use widget options to send bind time options
52     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
53             "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
54 
DefaultLayoutParser(Context context, LauncherWidgetHolder appWidgetHolder, LayoutParserCallback callback, Resources sourceRes, int layoutId)55     public DefaultLayoutParser(Context context, LauncherWidgetHolder appWidgetHolder,
56             LayoutParserCallback callback, Resources sourceRes, int layoutId) {
57         super(context, appWidgetHolder, callback, sourceRes, layoutId, TAG_FAVORITES);
58     }
59 
60     @Override
getFolderElementsMap()61     protected ArrayMap<String, TagParser> getFolderElementsMap() {
62         ArrayMap<String, TagParser> parsers = new ArrayMap<>();
63         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
64         parsers.put(TAG_SHORTCUT, new ShortcutParser());
65         return parsers;
66     }
67 
68     @Override
getLayoutElementsMap()69     protected ArrayMap<String, TagParser> getLayoutElementsMap() {
70         ArrayMap<String, TagParser> parsers = new ArrayMap<>();
71         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
72         parsers.put(TAG_APPWIDGET, new AppWidgetParser());
73         parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
74         parsers.put(TAG_SHORTCUT, new ShortcutParser());
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(XmlPullParser parser, int[] out)82     protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
83         out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
84         String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
85         if (strContainer != null) {
86             out[0] = Integer.parseInt(strContainer);
87         }
88         out[1] = Integer.parseInt(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(XmlPullParser parser)97         protected int invalidPackageOrClass(XmlPullParser 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      * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
182      */
183     public class ResolveParser implements TagParser {
184 
185         private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
186 
187         @Override
parseAndAdd(XmlPullParser parser)188         public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
189                 IOException {
190             final int groupDepth = parser.getDepth();
191             int type;
192             int addedId = -1;
193             while ((type = parser.next()) != XmlPullParser.END_TAG ||
194                     parser.getDepth() > groupDepth) {
195                 if (type != XmlPullParser.START_TAG || addedId > -1) {
196                     continue;
197                 }
198                 final String fallback_item_name = parser.getName();
199                 if (TAG_FAVORITE.equals(fallback_item_name)) {
200                     addedId = mChildParser.parseAndAdd(parser);
201                 } else {
202                     Log.e(TAG, "Fallback groups can contain only favorites, found "
203                             + fallback_item_name);
204                 }
205             }
206             return addedId;
207         }
208     }
209 
210     /**
211      * A parser which adds a folder whose contents come from partner apk.
212      */
213     @Thunk
214     class PartnerFolderParser implements TagParser {
215 
216         @Override
parseAndAdd(XmlPullParser parser)217         public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
218                 IOException {
219             // Folder contents come from an external XML resource
220             final Partner partner = Partner.get(mPackageManager);
221             if (partner != null) {
222                 final int resId = partner.getXmlResId(RES_PARTNER_FOLDER);
223                 if (resId != 0) {
224                     final XmlPullParser partnerParser = partner.getResources().getXml(resId);
225                     beginDocument(partnerParser, TAG_FOLDER);
226                     FolderParser folderParser = new FolderParser(getFolderElementsMap());
227                     return folderParser.parseAndAdd(partnerParser);
228                 }
229             }
230             return -1;
231         }
232     }
233 
234     /**
235      * An extension of FolderParser which allows adding items from a different xml.
236      */
237     @Thunk
238     class MyFolderParser extends FolderParser {
239 
240         @Override
parseAndAdd(XmlPullParser parser)241         public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
242                 IOException {
243             final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
244             if (resId != 0) {
245                 parser = mSourceRes.getXml(resId);
246                 beginDocument(parser, TAG_FOLDER);
247             }
248             return super.parseAndAdd(parser);
249         }
250     }
251 
252 
253     /**
254      * AppWidget parser which enforces that the app is already installed when the layout is parsed.
255      */
256     protected class AppWidgetParser extends PendingWidgetParser {
257 
258         @Override
verifyAndInsert(ComponentName cn, Bundle extras)259         protected int verifyAndInsert(ComponentName cn, Bundle extras) {
260             try {
261                 mPackageManager.getReceiverInfo(cn, 0);
262             } catch (Exception e) {
263                 String[] packages = mPackageManager.currentToCanonicalPackageNames(
264                         new String[]{cn.getPackageName()});
265                 cn = new ComponentName(packages[0], cn.getClassName());
266                 try {
267                     mPackageManager.getReceiverInfo(cn, 0);
268                 } catch (Exception e1) {
269                     Log.d(TAG, "Can't find widget provider: " + cn.getClassName());
270                     return -1;
271                 }
272             }
273 
274             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
275             int insertedId = -1;
276             try {
277                 int appWidgetId = mAppWidgetHolder.allocateAppWidgetId();
278 
279                 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
280                     Log.e(TAG, "Unable to bind app widget id " + cn);
281                     mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
282                     return -1;
283                 }
284 
285                 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
286                 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
287                 mValues.put(Favorites._ID, mCallback.generateNewItemId());
288                 insertedId = mCallback.insertAndCheck(mDb, mValues);
289                 if (insertedId < 0) {
290                     mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
291                     return insertedId;
292                 }
293 
294                 // Send a broadcast to configure the widget
295                 if (!extras.isEmpty()) {
296                     Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
297                     intent.setComponent(cn);
298                     intent.putExtras(extras);
299                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
300                     mContext.sendBroadcast(intent);
301                 }
302             } catch (RuntimeException ex) {
303                 Log.e(TAG, "Problem allocating appWidgetId", ex);
304             }
305             return insertedId;
306         }
307     }
308 }
309