Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3;
     18 
     19 import android.appwidget.AppWidgetHost;
     20 import android.appwidget.AppWidgetManager;
     21 import android.content.ComponentName;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.ActivityInfo;
     26 import android.content.pm.PackageManager;
     27 import android.content.res.Resources;
     28 import android.content.res.XmlResourceParser;
     29 import android.database.sqlite.SQLiteDatabase;
     30 import android.graphics.drawable.Drawable;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.text.TextUtils;
     34 import android.util.Log;
     35 import android.util.Pair;
     36 import android.util.Patterns;
     37 
     38 import com.android.launcher3.LauncherProvider.SqlArguments;
     39 import com.android.launcher3.LauncherSettings.Favorites;
     40 
     41 import org.xmlpull.v1.XmlPullParser;
     42 import org.xmlpull.v1.XmlPullParserException;
     43 
     44 import java.io.IOException;
     45 import java.util.ArrayList;
     46 import java.util.HashMap;
     47 
     48 /**
     49  * Layout parsing code for auto installs layout
     50  */
     51 public class AutoInstallsLayout {
     52     private static final String TAG = "AutoInstalls";
     53     private static final boolean LOGD = false;
     54 
     55     /** Marker action used to discover a package which defines launcher customization */
     56     static final String ACTION_LAUNCHER_CUSTOMIZATION =
     57             "android.autoinstalls.config.action.PLAY_AUTO_INSTALL";
     58 
     59     private static final String LAYOUT_RES = "default_layout";
     60 
     61     static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost,
     62             LayoutParserCallback callback) {
     63         Pair<String, Resources> customizationApkInfo = Utilities.findSystemApk(
     64                 ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager());
     65         if (customizationApkInfo == null) {
     66             return null;
     67         }
     68 
     69         String pkg = customizationApkInfo.first;
     70         Resources res = customizationApkInfo.second;
     71         int layoutId = res.getIdentifier(LAYOUT_RES, "xml", pkg);
     72         if (layoutId == 0) {
     73             Log.e(TAG, "Layout definition not found in package: " + pkg);
     74             return null;
     75         }
     76         return new AutoInstallsLayout(context, appWidgetHost, callback, res, layoutId,
     77                 TAG_WORKSPACE);
     78     }
     79 
     80     // Object Tags
     81     private static final String TAG_WORKSPACE = "workspace";
     82     private static final String TAG_APP_ICON = "appicon";
     83     private static final String TAG_AUTO_INSTALL = "autoinstall";
     84     private static final String TAG_FOLDER = "folder";
     85     private static final String TAG_APPWIDGET = "appwidget";
     86     private static final String TAG_SHORTCUT = "shortcut";
     87     private static final String TAG_EXTRA = "extra";
     88 
     89     private static final String ATTR_CONTAINER = "container";
     90     private static final String ATTR_RANK = "rank";
     91 
     92     private static final String ATTR_PACKAGE_NAME = "packageName";
     93     private static final String ATTR_CLASS_NAME = "className";
     94     private static final String ATTR_TITLE = "title";
     95     private static final String ATTR_SCREEN = "screen";
     96     private static final String ATTR_X = "x";
     97     private static final String ATTR_Y = "y";
     98     private static final String ATTR_SPAN_X = "spanX";
     99     private static final String ATTR_SPAN_Y = "spanY";
    100     private static final String ATTR_ICON = "icon";
    101     private static final String ATTR_URL = "url";
    102 
    103     // Style attrs -- "Extra"
    104     private static final String ATTR_KEY = "key";
    105     private static final String ATTR_VALUE = "value";
    106 
    107     private static final String HOTSEAT_CONTAINER_NAME =
    108             Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
    109 
    110     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
    111             "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
    112 
    113     private final Context mContext;
    114     private final AppWidgetHost mAppWidgetHost;
    115     private final LayoutParserCallback mCallback;
    116 
    117     protected final PackageManager mPackageManager;
    118     protected final Resources mSourceRes;
    119     protected final int mLayoutId;
    120 
    121     private final int mHotseatAllAppsRank;
    122 
    123     private final long[] mTemp = new long[2];
    124     private final ContentValues mValues;
    125     private final String mRootTag;
    126 
    127     protected SQLiteDatabase mDb;
    128 
    129     public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
    130             LayoutParserCallback callback, Resources res,
    131             int layoutId, String rootTag) {
    132         mContext = context;
    133         mAppWidgetHost = appWidgetHost;
    134         mCallback = callback;
    135 
    136         mPackageManager = context.getPackageManager();
    137         mValues = new ContentValues();
    138         mRootTag = rootTag;
    139 
    140         mSourceRes = res;
    141         mLayoutId = layoutId;
    142         mHotseatAllAppsRank = LauncherAppState.getInstance()
    143                 .getDynamicGrid().getDeviceProfile().hotseatAllAppsRank;
    144     }
    145 
    146     /**
    147      * Loads the layout in the db and returns the number of entries added on the desktop.
    148      */
    149     public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
    150         mDb = db;
    151         try {
    152             return parseLayout(mLayoutId, screenIds);
    153         } catch (Exception e) {
    154             Log.w(TAG, "Got exception parsing layout.", e);
    155             return -1;
    156         }
    157     }
    158 
    159     /**
    160      * Parses the layout and returns the number of elements added on the homescreen.
    161      */
    162     protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
    163             throws XmlPullParserException, IOException {
    164         XmlResourceParser parser = mSourceRes.getXml(layoutId);
    165         beginDocument(parser, mRootTag);
    166         final int depth = parser.getDepth();
    167         int type;
    168         HashMap<String, TagParser> tagParserMap = getLayoutElementsMap();
    169         int count = 0;
    170 
    171         while (((type = parser.next()) != XmlPullParser.END_TAG ||
    172                 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
    173             if (type != XmlPullParser.START_TAG) {
    174                 continue;
    175             }
    176             count += parseAndAddNode(parser, tagParserMap, screenIds);
    177         }
    178         return count;
    179     }
    180 
    181     /**
    182      * Parses container and screenId attribute from the current tag, and puts it in the out.
    183      * @param out array of size 2.
    184      */
    185     protected void parseContainerAndScreen(XmlResourceParser parser, long[] out) {
    186         if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
    187             out[0] = Favorites.CONTAINER_HOTSEAT;
    188             // Hack: hotseat items are stored using screen ids
    189             long rank = Long.parseLong(getAttributeValue(parser, ATTR_RANK));
    190             out[1] = (rank < mHotseatAllAppsRank) ? rank : (rank + 1);
    191         } else {
    192             out[0] = Favorites.CONTAINER_DESKTOP;
    193             out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
    194         }
    195     }
    196 
    197     /**
    198      * Parses the current node and returns the number of elements added.
    199      */
    200     protected int parseAndAddNode(
    201             XmlResourceParser parser,
    202             HashMap<String, TagParser> tagParserMap,
    203             ArrayList<Long> screenIds)
    204                     throws XmlPullParserException, IOException {
    205         mValues.clear();
    206         parseContainerAndScreen(parser, mTemp);
    207         final long container = mTemp[0];
    208         final long screenId = mTemp[1];
    209 
    210         mValues.put(Favorites.CONTAINER, container);
    211         mValues.put(Favorites.SCREEN, screenId);
    212         mValues.put(Favorites.CELLX, getAttributeValue(parser, ATTR_X));
    213         mValues.put(Favorites.CELLY, getAttributeValue(parser, ATTR_Y));
    214 
    215         TagParser tagParser = tagParserMap.get(parser.getName());
    216         if (tagParser == null) {
    217             if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName());
    218             return 0;
    219         }
    220         long newElementId = tagParser.parseAndAdd(parser);
    221         if (newElementId >= 0) {
    222             // Keep track of the set of screens which need to be added to the db.
    223             if (!screenIds.contains(screenId) &&
    224                     container == Favorites.CONTAINER_DESKTOP) {
    225                 screenIds.add(screenId);
    226             }
    227             return 1;
    228         }
    229         return 0;
    230     }
    231 
    232     protected long addShortcut(String title, Intent intent, int type) {
    233         long id = mCallback.generateNewItemId();
    234         mValues.put(Favorites.INTENT, intent.toUri(0));
    235         mValues.put(Favorites.TITLE, title);
    236         mValues.put(Favorites.ITEM_TYPE, type);
    237         mValues.put(Favorites.SPANX, 1);
    238         mValues.put(Favorites.SPANY, 1);
    239         mValues.put(Favorites._ID, id);
    240         if (mCallback.insertAndCheck(mDb, mValues) < 0) {
    241             return -1;
    242         } else {
    243             return id;
    244         }
    245     }
    246 
    247     protected HashMap<String, TagParser> getFolderElementsMap() {
    248         HashMap<String, TagParser> parsers = new HashMap<String, TagParser>();
    249         parsers.put(TAG_APP_ICON, new AppShortcutParser());
    250         parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
    251         parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
    252         return parsers;
    253     }
    254 
    255     protected HashMap<String, TagParser> getLayoutElementsMap() {
    256         HashMap<String, TagParser> parsers = new HashMap<String, TagParser>();
    257         parsers.put(TAG_APP_ICON, new AppShortcutParser());
    258         parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
    259         parsers.put(TAG_FOLDER, new FolderParser());
    260         parsers.put(TAG_APPWIDGET, new AppWidgetParser());
    261         parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
    262         return parsers;
    263     }
    264 
    265     protected interface TagParser {
    266         /**
    267          * Parses the tag and adds to the db
    268          * @return the id of the row added or -1;
    269          */
    270         long parseAndAdd(XmlResourceParser parser)
    271                 throws XmlPullParserException, IOException;
    272     }
    273 
    274     /**
    275      * App shortcuts: required attributes packageName and className
    276      */
    277     protected class AppShortcutParser implements TagParser {
    278 
    279         @Override
    280         public long parseAndAdd(XmlResourceParser parser) {
    281             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
    282             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
    283 
    284             if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
    285                 ActivityInfo info;
    286                 try {
    287                     ComponentName cn;
    288                     try {
    289                         cn = new ComponentName(packageName, className);
    290                         info = mPackageManager.getActivityInfo(cn, 0);
    291                     } catch (PackageManager.NameNotFoundException nnfe) {
    292                         String[] packages = mPackageManager.currentToCanonicalPackageNames(
    293                                 new String[] { packageName });
    294                         cn = new ComponentName(packages[0], className);
    295                         info = mPackageManager.getActivityInfo(cn, 0);
    296                     }
    297                     final Intent intent = new Intent(Intent.ACTION_MAIN, null)
    298                         .addCategory(Intent.CATEGORY_LAUNCHER)
    299                         .setComponent(cn)
    300                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    301                                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    302 
    303                     return addShortcut(info.loadLabel(mPackageManager).toString(),
    304                             intent, Favorites.ITEM_TYPE_APPLICATION);
    305                 } catch (PackageManager.NameNotFoundException e) {
    306                     if (LOGD) Log.w(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
    307                 }
    308                 return -1;
    309             } else {
    310                 return invalidPackageOrClass(parser);
    311             }
    312         }
    313 
    314         /**
    315          * Helper method to allow extending the parser capabilities
    316          */
    317         protected long invalidPackageOrClass(XmlResourceParser parser) {
    318             if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
    319             return -1;
    320         }
    321     }
    322 
    323     /**
    324      * AutoInstall: required attributes packageName and className
    325      */
    326     protected class AutoInstallParser implements TagParser {
    327 
    328         @Override
    329         public long parseAndAdd(XmlResourceParser parser) {
    330             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
    331             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
    332             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
    333                 if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
    334                 return -1;
    335             }
    336 
    337             mValues.put(Favorites.RESTORED, ShortcutInfo.FLAG_AUTOINTALL_ICON);
    338             final Intent intent = new Intent(Intent.ACTION_MAIN, null)
    339                 .addCategory(Intent.CATEGORY_LAUNCHER)
    340                 .setComponent(new ComponentName(packageName, className))
    341                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    342                         Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    343             return addShortcut(mContext.getString(R.string.package_state_unknown), intent,
    344                     Favorites.ITEM_TYPE_APPLICATION);
    345         }
    346     }
    347 
    348     /**
    349      * Parses a web shortcut. Required attributes url, icon, title
    350      */
    351     protected class ShortcutParser implements TagParser {
    352 
    353         private final Resources mIconRes;
    354 
    355         public ShortcutParser(Resources iconRes) {
    356             mIconRes = iconRes;
    357         }
    358 
    359         @Override
    360         public long parseAndAdd(XmlResourceParser parser) {
    361             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
    362             final int iconId = getAttributeResourceValue(parser, ATTR_ICON, 0);
    363 
    364             if (titleResId == 0 || iconId == 0) {
    365                 if (LOGD) Log.d(TAG, "Ignoring shortcut");
    366                 return -1;
    367             }
    368 
    369             final Intent intent = parseIntent(parser);
    370             if (intent == null) {
    371                 return -1;
    372             }
    373 
    374             Drawable icon = mIconRes.getDrawable(iconId);
    375             if (icon == null) {
    376                 if (LOGD) Log.d(TAG, "Ignoring shortcut, can't load icon");
    377                 return -1;
    378             }
    379 
    380             ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext));
    381             mValues.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
    382             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
    383             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
    384 
    385             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    386                         Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    387             return addShortcut(mSourceRes.getString(titleResId),
    388                     intent, Favorites.ITEM_TYPE_SHORTCUT);
    389         }
    390 
    391         protected Intent parseIntent(XmlResourceParser parser) {
    392             final String url = getAttributeValue(parser, ATTR_URL);
    393             if (TextUtils.isEmpty(url) || !Patterns.WEB_URL.matcher(url).matches()) {
    394                 if (LOGD) Log.d(TAG, "Ignoring shortcut, invalid url: " + url);
    395                 return null;
    396             }
    397             return new Intent(Intent.ACTION_VIEW, null).setData(Uri.parse(url));
    398         }
    399     }
    400 
    401     /**
    402      * AppWidget parser: Required attributes packageName, className, spanX and spanY.
    403      * Options child nodes: <extra key=... value=... />
    404      */
    405     protected class AppWidgetParser implements TagParser {
    406 
    407         @Override
    408         public long parseAndAdd(XmlResourceParser parser)
    409                 throws XmlPullParserException, IOException {
    410             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
    411             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
    412             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
    413                 if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
    414                 return -1;
    415             }
    416 
    417             ComponentName cn = new ComponentName(packageName, className);
    418             try {
    419                 mPackageManager.getReceiverInfo(cn, 0);
    420             } catch (Exception e) {
    421                 String[] packages = mPackageManager.currentToCanonicalPackageNames(
    422                         new String[] { packageName });
    423                 cn = new ComponentName(packages[0], className);
    424                 try {
    425                     mPackageManager.getReceiverInfo(cn, 0);
    426                 } catch (Exception e1) {
    427                     if (LOGD) Log.d(TAG, "Can't find widget provider: " + className);
    428                     return -1;
    429                 }
    430             }
    431 
    432             mValues.put(Favorites.SPANX, getAttributeValue(parser, ATTR_SPAN_X));
    433             mValues.put(Favorites.SPANY, getAttributeValue(parser, ATTR_SPAN_Y));
    434 
    435             // Read the extras
    436             Bundle extras = new Bundle();
    437             int widgetDepth = parser.getDepth();
    438             int type;
    439             while ((type = parser.next()) != XmlPullParser.END_TAG ||
    440                     parser.getDepth() > widgetDepth) {
    441                 if (type != XmlPullParser.START_TAG) {
    442                     continue;
    443                 }
    444 
    445                 if (TAG_EXTRA.equals(parser.getName())) {
    446                     String key = getAttributeValue(parser, ATTR_KEY);
    447                     String value = getAttributeValue(parser, ATTR_VALUE);
    448                     if (key != null && value != null) {
    449                         extras.putString(key, value);
    450                     } else {
    451                         throw new RuntimeException("Widget extras must have a key and value");
    452                     }
    453                 } else {
    454                     throw new RuntimeException("Widgets can contain only extras");
    455                 }
    456             }
    457 
    458             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
    459             long insertedId = -1;
    460             try {
    461                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
    462 
    463                 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
    464                     if (LOGD) Log.e(TAG, "Unable to bind app widget id " + cn);
    465                     return -1;
    466                 }
    467 
    468                 mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
    469                 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
    470                 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
    471                 mValues.put(Favorites._ID, mCallback.generateNewItemId());
    472                 insertedId = mCallback.insertAndCheck(mDb, mValues);
    473                 if (insertedId < 0) {
    474                     mAppWidgetHost.deleteAppWidgetId(appWidgetId);
    475                     return insertedId;
    476                 }
    477 
    478                 // Send a broadcast to configure the widget
    479                 if (!extras.isEmpty()) {
    480                     Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
    481                     intent.setComponent(cn);
    482                     intent.putExtras(extras);
    483                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
    484                     mContext.sendBroadcast(intent);
    485                 }
    486             } catch (RuntimeException ex) {
    487                 if (LOGD) Log.e(TAG, "Problem allocating appWidgetId", ex);
    488             }
    489             return insertedId;
    490         }
    491     }
    492 
    493     protected class FolderParser implements TagParser {
    494         private final HashMap<String, TagParser> mFolderElements;
    495 
    496         public FolderParser() {
    497             this(getFolderElementsMap());
    498         }
    499 
    500         public FolderParser(HashMap<String, TagParser> elements) {
    501             mFolderElements = elements;
    502         }
    503 
    504         @Override
    505         public long parseAndAdd(XmlResourceParser parser)
    506                 throws XmlPullParserException, IOException {
    507             final String title;
    508             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
    509             if (titleResId != 0) {
    510                 title = mSourceRes.getString(titleResId);
    511             } else {
    512                 title = mContext.getResources().getString(R.string.folder_name);
    513             }
    514 
    515             mValues.put(Favorites.TITLE, title);
    516             mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
    517             mValues.put(Favorites.SPANX, 1);
    518             mValues.put(Favorites.SPANY, 1);
    519             mValues.put(Favorites._ID, mCallback.generateNewItemId());
    520             long folderId = mCallback.insertAndCheck(mDb, mValues);
    521             if (folderId < 0) {
    522                 if (LOGD) Log.e(TAG, "Unable to add folder");
    523                 return -1;
    524             }
    525 
    526             final ContentValues myValues = new ContentValues(mValues);
    527             ArrayList<Long> folderItems = new ArrayList<Long>();
    528 
    529             int type;
    530             int folderDepth = parser.getDepth();
    531             while ((type = parser.next()) != XmlPullParser.END_TAG ||
    532                     parser.getDepth() > folderDepth) {
    533                 if (type != XmlPullParser.START_TAG) {
    534                     continue;
    535                 }
    536                 mValues.clear();
    537                 mValues.put(Favorites.CONTAINER, folderId);
    538 
    539                 TagParser tagParser = mFolderElements.get(parser.getName());
    540                 if (tagParser != null) {
    541                     final long id = tagParser.parseAndAdd(parser);
    542                     if (id >= 0) {
    543                         folderItems.add(id);
    544                     }
    545                 } else {
    546                     throw new RuntimeException("Invalid folder item " + parser.getName());
    547                 }
    548             }
    549 
    550             long addedId = folderId;
    551 
    552             // We can only have folders with >= 2 items, so we need to remove the
    553             // folder and clean up if less than 2 items were included, or some
    554             // failed to add, and less than 2 were actually added
    555             if (folderItems.size() < 2) {
    556                 // Delete the folder
    557                 Uri uri = Favorites.getContentUri(folderId, false);
    558                 SqlArguments args = new SqlArguments(uri, null, null);
    559                 mDb.delete(args.table, args.where, args.args);
    560                 addedId = -1;
    561 
    562                 // If we have a single item, promote it to where the folder
    563                 // would have been.
    564                 if (folderItems.size() == 1) {
    565                     final ContentValues childValues = new ContentValues();
    566                     copyInteger(myValues, childValues, Favorites.CONTAINER);
    567                     copyInteger(myValues, childValues, Favorites.SCREEN);
    568                     copyInteger(myValues, childValues, Favorites.CELLX);
    569                     copyInteger(myValues, childValues, Favorites.CELLY);
    570 
    571                     addedId = folderItems.get(0);
    572                     mDb.update(LauncherProvider.TABLE_FAVORITES, childValues,
    573                             Favorites._ID + "=" + addedId, null);
    574                 }
    575             }
    576             return addedId;
    577         }
    578     }
    579 
    580     protected static final void beginDocument(XmlPullParser parser, String firstElementName)
    581             throws XmlPullParserException, IOException {
    582         int type;
    583         while ((type = parser.next()) != XmlPullParser.START_TAG
    584                 && type != XmlPullParser.END_DOCUMENT);
    585 
    586         if (type != XmlPullParser.START_TAG) {
    587             throw new XmlPullParserException("No start tag found");
    588         }
    589 
    590         if (!parser.getName().equals(firstElementName)) {
    591             throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
    592                     ", expected " + firstElementName);
    593         }
    594     }
    595 
    596     /**
    597      * Return attribute value, attempting launcher-specific namespace first
    598      * before falling back to anonymous attribute.
    599      */
    600     protected static String getAttributeValue(XmlResourceParser parser, String attribute) {
    601         String value = parser.getAttributeValue(
    602                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
    603         if (value == null) {
    604             value = parser.getAttributeValue(null, attribute);
    605         }
    606         return value;
    607     }
    608 
    609     /**
    610      * Return attribute resource value, attempting launcher-specific namespace
    611      * first before falling back to anonymous attribute.
    612      */
    613     protected static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
    614             int defaultValue) {
    615         int value = parser.getAttributeResourceValue(
    616                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
    617                 defaultValue);
    618         if (value == defaultValue) {
    619             value = parser.getAttributeResourceValue(null, attribute, defaultValue);
    620         }
    621         return value;
    622     }
    623 
    624     public static interface LayoutParserCallback {
    625         long generateNewItemId();
    626 
    627         long insertAndCheck(SQLiteDatabase db, ContentValues values);
    628     }
    629 
    630     private static void copyInteger(ContentValues from, ContentValues to, String key) {
    631         to.put(key, from.getAsInteger(key));
    632     }
    633 }
    634