Android13-Launcer3_配置文件信息添加到favorites表中过程

LoadTask在读取相关数据前,会把配置文件数据写入到数据库的favorites表中

调用LauncherProvider的loadDefaultFavoritesIfNecessary()方法,根据配置文件构建对应的AutoInstallsLayout对象,这里以AOSP的device_profiles.xml配置文件中defaultLayoutId属性指定的配置文件为例,比如default_workspace_5x5.xml

通过xml解析器解析配置文件,通过resolve标签获取从属于哪个页面还是属于Hotseat,以及在页面中第几行第几列信息,favorite标签的uri属性构建intent信息,解析完数据后,再通过回调DatabaseHelper插入到数据库的favorites表中

在LoadTask的run()加载数据库表favorites前

java 复制代码
    protected void loadWorkspace(
            List<ShortcutInfo> allDeepShortcuts,
            Uri contentUri,
            String selection,
            @Nullable LoaderMemoryLogger logger) {
            
        ...
            
        if (clearDb) {
            Log.d(TAG, "loadWorkspace: resetting launcher database");
            //调到LauncherProvider的call方法中的METHOD_CREATE_EMPTY_DB逻辑
            LauncherSettings.Settings.call(contentResolver,
                    LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
        }

        Log.d(TAG, "loadWorkspace: loading default favorites");
        //调到LauncherProvider的call方法中的METHOD_LOAD_DEFAULT_FAVORITES逻辑
        LauncherSettings.Settings.call(contentResolver,
                LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
        
        synchronized (mBgDataModel) {
            mBgDataModel.clear();
            mPendingPackages.clear();
            
        ...

LauncherProvider

java 复制代码
    @Override
    public Bundle call(String method, final String arg, final Bundle extras) {
        if (Binder.getCallingUid() != Process.myUid()) {
            return null;
        }
        createDbIfNotExists();

        switch (method) {
            
            ...
            case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
                loadDefaultFavoritesIfNecessary();
                return null;
            }
java 复制代码
    /**
     * Loads the default workspace based on the following priority scheme:
     *   1) From the app restrictions
     *   2) From a package provided by play store
     *   3) From a partner configuration APK, already in the system image
     *   4) The default configuration for the particular device
     */
    synchronized private void loadDefaultFavoritesIfNecessary() {
        SharedPreferences sp = LauncherPrefs.getPrefs(getContext());

        //数据库是否创建
        if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
            Log.d(TAG, "loading default workspace");

            LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
            //根据不同的文件构建出AutoInstallsLayout 
            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
            if (loader == null) {
                loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
            }
            if (loader == null) {
                final Partner partner = Partner.get(getContext().getPackageManager());
                if (partner != null) {
                    //partner_default_layout文件
                    int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
                    if (workspaceResId != 0) {
                        loader = new DefaultLayoutParser(getContext(), widgetHolder,
                                mOpenHelper, partner.getResources(), workspaceResId);
                    }
                }
            }

            final boolean usingExternallyProvidedLayout = loader != null;
            if (loader == null) {
                //会根据条件通过InvariantDeviceProfile的defaultLayoutId进行构建
                loader = getDefaultLayoutParser(widgetHolder);
            }

            // There might be some partially restored DB items, due to buggy restore logic in
            // previous versions of launcher.
            mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
            // Populate favorites table with initial favorites
            //解析类似default_workspace_5x5.xml文件,并把数据插入到数据库中
            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                    && usingExternallyProvidedLayout) {
                // Unable to load external layout. Cleanup and load the internal layout.
                mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                        getDefaultLayoutParser(widgetHolder));
            }
            clearFlagEmptyDbCreated();
            widgetHolder.destroy();
        }
    }

AutoInstallsLayout的get()逻辑

java 复制代码
    /** Marker action used to discover a package which defines launcher customization */
    static final String ACTION_LAUNCHER_CUSTOMIZATION =
            "android.autoinstalls.config.action.PLAY_AUTO_INSTALL";
    /**
     * Layout resource which also includes grid size and hotseat count, e.g., default_layout_6x6_h5
     */
    private static final String FORMATTED_LAYOUT_RES_WITH_HOSTEAT = "default_layout_%dx%d_h%s";
    private static final String FORMATTED_LAYOUT_RES = "default_layout_%dx%d";
    private static final String LAYOUT_RES = "default_layout";

    static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder,
            LayoutParserCallback callback) {
        Partner partner = Partner.get(context.getPackageManager(), ACTION_LAUNCHER_CUSTOMIZATION);
        if (partner == null) {
            return null;
        }
        InvariantDeviceProfile grid = LauncherAppState.getIDP(context);

        // Try with grid size and hotseat count
        //构建文件名字,比如default_layout_6x6_h5
        String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
                grid.numColumns, grid.numRows, grid.numDatabaseHotseatIcons);
        int layoutId = partner.getXmlResId(layoutName);

        // Try with only grid size
        if (layoutId == 0) {
            Log.d(TAG, "Formatted layout: " + layoutName
                    + " not found. Trying layout without hosteat");
            //构建文件名字,比如default_layout_6x6
            layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
                    grid.numColumns, grid.numRows);
            layoutId = partner.getXmlResId(layoutName);
        }

        // Try the default layout
        if (layoutId == 0) {
            Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout");
            //default_layout
            layoutId = partner.getXmlResId(LAYOUT_RES);
        }

        if (layoutId == 0) {
            Log.e(TAG, "Layout definition not found in package: " + partner.getPackageName());
            return null;
        }
        return new AutoInstallsLayout(context, appWidgetHolder, callback, partner.getResources(),
                layoutId, TAG_WORKSPACE);
    }

getDefaultLayoutParser()方法逻辑

java 复制代码
    private int mDefaultWorkspaceLayoutOverride = 0;
    
    private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) {
        InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
        //mDefaultWorkspaceLayoutOverride 默认为0,在call方法中会被修改
        //idp.defaultLayoutId是InvariantDeviceProfile解析device_profiles.xml配置获取的
        int defaultLayout = mDefaultWorkspaceLayoutOverride > 0
                ? mDefaultWorkspaceLayoutOverride : idp.defaultLayoutId;

        if (getContext().getSystemService(UserManager.class).isDemoUser()
                && idp.demoModeLayoutId != 0) {
            defaultLayout = idp.demoModeLayoutId;
        }

        return new DefaultLayoutParser(getContext(), widgetHolder,
                mOpenHelper, getContext().getResources(), defaultLayout);
    }

device_profiles.xml

java 复制代码
<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >

		...

    <grid-option
        launcher:name="5_by_5"
        launcher:numRows="5"
        launcher:numColumns="5"
        launcher:numFolderRows="4"
        launcher:numFolderColumns="4"
        launcher:numHotseatIcons="5"
        launcher:numExtendedHotseatIcons="6"
        launcher:dbFile="launcher.db"
        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
        launcher:defaultLayoutId="@xml/default_workspace_5x5"
        launcher:deviceCategory="phone|multi_display" >

这里就配置了default_workspace_5x5.xml文件,会存储到InvariantDeviceProfile的defaultLayoutId变量

default_workspace_5x5.xml

java 复制代码
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">

    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
    <!-- Dialer, Messaging, [Maps/Music], Browser, Camera -->
    <resolve
        launcher:container="-101"
        launcher:screen="0"
        launcher:x="0"
        launcher:y="0" >
        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
        <favorite launcher:uri="tel:123" />
        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
    </resolve>
    
    ...
    
    <resolve
        launcher:screen="0"
        launcher:x="1"
        launcher:y="-1" >
	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
	    <favorite launcher:uri="#Intent;type=images/*;end" />

launcher:y="-1"如果是负数,会和行数进行相加

LauncherProvider内部类DatabaseHelper的loadFavorites()方法逻辑

java 复制代码
        @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
            // TODO: Use multiple loaders with fall-back and transaction.
            //交由AutoInstallsLayout处理
            int count = loader.loadLayout(db, new IntArray());

            // Ensure that the max ids are initialized
            mMaxItemId = initializeMaxItemId(db);
            return count;
        }

AutoInstallsLayout

java 复制代码
    /**
     * Loads the layout in the db and returns the number of entries added on the desktop.
     */
    public int loadLayout(SQLiteDatabase db, IntArray screenIds) {
        mDb = db;
        try {
            //mInitialLayoutSupplier.get(),xml解析器
            return parseLayout(mInitialLayoutSupplier.get(), screenIds);
        } catch (Exception e) {
            Log.e(TAG, "Error parsing layout: ", e);
            return -1;
        }
    }
    
    protected int parseLayout(XmlPullParser parser, IntArray screenIds)
            throws XmlPullParserException, IOException {
        beginDocument(parser, mRootTag);
        final int depth = parser.getDepth();
        int type;
        
        //default_workspace_5x5.xml对应的对象是DefaultLayoutParser,DefaultLayoutParser继承了AutoInstallsLayout
        //DefaultLayoutParser重写了getLayoutElementsMap
        ArrayMap<String, TagParser> tagParserMap = getLayoutElementsMap();
        int count = 0;

        //遍历xml文件中的标签
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            //解析
            count += parseAndAddNode(parser, tagParserMap, screenIds);
        }
        return count;
    }
    /**
     * Parses the current node and returns the number of elements added.
     */
    protected int parseAndAddNode(
            XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
            throws XmlPullParserException, IOException {

        //include标签,包含其他配置文件
        if (TAG_INCLUDE.equals(parser.getName())) {
            final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
            if (resId != 0) {
                // recursively load some more favorites, why not?
                return parseLayout(mSourceRes.getXml(resId), screenIds);
            } else {
                return 0;
            }
        }

        mValues.clear();
        //解析出标签中container 和screenId 属性值
        //container 为-101是Hotseat的图标
        parseContainerAndScreen(parser, mTemp);
        final int container = mTemp[0];
        final int screenId = mTemp[1];

        mValues.put(Favorites.CONTAINER, container);
        mValues.put(Favorites.SCREEN, screenId);

        //x,排列在第几行
        mValues.put(Favorites.CELLX,
                convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
        //y,排列在第几列
        mValues.put(Favorites.CELLY,
                convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));

        TagParser tagParser = tagParserMap.get(parser.getName());
        if (tagParser == null) {
            if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName());
            return 0;
        }
        //交由对应的标签解析器处理
        int newElementId = tagParser.parseAndAdd(parser);
        if (newElementId >= 0) {
            // Keep track of the set of screens which need to be added to the db.
            if (!screenIds.contains(screenId) &&
                    container == Favorites.CONTAINER_DESKTOP) {
                screenIds.add(screenId);
            }
            return 1;
        }
        return 0;
    }
    
    //public static final int CONTAINER_HOTSEAT = -101;
    private static final String HOTSEAT_CONTAINER_NAME =
            Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
    
    private static final String ATTR_CONTAINER = "container";
    private static final String ATTR_SCREEN = "screen";
    
    protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
        //ATTR_CONTAINER为container属性
        //ATTR_SCREEN 为screen属性
        if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
            out[0] = Favorites.CONTAINER_HOTSEAT;
            // Hack: hotseat items are stored using screen ids
            out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_RANK));
        } else {
            out[0] = Favorites.CONTAINER_DESKTOP;
            out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
        }
    }

DefaultLayoutParser

java 复制代码
		//对应default_workspace_5x5.xml配置相应标签
		protected static final String TAG_RESOLVE = "resolve";
    private static final String TAG_FAVORITES = "favorites";
    
    @Override
    protected ArrayMap<String, TagParser> getLayoutElementsMap() {
        ArrayMap<String, TagParser> parsers = new ArrayMap<>();
        parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
        parsers.put(TAG_APPWIDGET, new AppWidgetParser());
        parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
        parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
        parsers.put(TAG_RESOLVE, new ResolveParser());
        parsers.put(TAG_FOLDER, new MyFolderParser());
        parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
        return parsers;
    }

DefaultLayoutParser的内部类ResolveParser

java 复制代码
    /**
     * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
     */
    public class ResolveParser implements TagParser {

        private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();

        @Override
        public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
                IOException {
            final int groupDepth = parser.getDepth();
            int type;
            int addedId = -1;
            while ((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > groupDepth) {
                if (type != XmlPullParser.START_TAG || addedId > -1) {
                    continue;
                }
                final String fallback_item_name = parser.getName();
                if (TAG_FAVORITE.equals(fallback_item_name)) {
                    addedId = mChildParser.parseAndAdd(parser);
                } else {
                    Log.e(TAG, "Fallback groups can contain only favorites, found "
                            + fallback_item_name);
                }
            }
            return addedId;
        }
    }

AppShortcutWithUriParser 继承AppShortcutParser, AppShortcutParser是AutoInstallsLayout的内部类

java 复制代码
    //AutoInstallsLayout定义的
    private static final String ATTR_PACKAGE_NAME = "packageName";
    private static final String ATTR_CLASS_NAME = "className";
    
    protected class AppShortcutParser implements TagParser {

        @Override
        public int parseAndAdd(XmlPullParser parser) {
            //看上面default_workspace_5x5.xml配置没有这两个属性
            final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
            final String className = getAttributeValue(parser, ATTR_CLASS_NAME);

            if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
                ActivityInfo info;
                try {
                    ComponentName cn;
                    try {
                        cn = new ComponentName(packageName, className);
                        info = mPackageManager.getActivityInfo(cn, 0);
                    } catch (PackageManager.NameNotFoundException nnfe) {
                        String[] packages = mPackageManager.currentToCanonicalPackageNames(
                                new String[]{packageName});
                        cn = new ComponentName(packages[0], className);
                        info = mPackageManager.getActivityInfo(cn, 0);
                    }
                    final Intent intent = new Intent(Intent.ACTION_MAIN, null)
                            .addCategory(Intent.CATEGORY_LAUNCHER)
                            .setComponent(cn)
                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

                    return addShortcut(info.loadLabel(mPackageManager).toString(),
                            intent, Favorites.ITEM_TYPE_APPLICATION);
                } catch (PackageManager.NameNotFoundException e) {
                    Log.e(TAG, "Favorite not found: " + packageName + "/" + className);
                }
                return -1;
            } else {
                //走回AppShortcutWithUriParser 的方法
                return invalidPackageOrClass(parser);
            }
        }

        /**
         * Helper method to allow extending the parser capabilities
         */
        protected int invalidPackageOrClass(XmlPullParser parser) {
            Log.w(TAG, "Skipping invalid <favorite> with no component");
            return -1;
        }
    }

AppShortcutWithUriParser 的invalidPackageOrClass()方法

java 复制代码
        protected static final String ATTR_URI = "uri";
        
        @Override
        protected int invalidPackageOrClass(XmlPullParser parser) {
            //获取uri属性值
            final String uri = getAttributeValue(parser, ATTR_URI);
            if (TextUtils.isEmpty(uri)) {
                Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
                return -1;
            }

            final Intent metaIntent;
            try {
                metaIntent = Intent.parseUri(uri, 0);
            } catch (URISyntaxException e) {
                Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
                return -1;
            }

            ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
                    PackageManager.MATCH_DEFAULT_ONLY);
            final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
                    metaIntent, PackageManager.MATCH_DEFAULT_ONLY);

            // Verify that the result is an app and not just the resolver dialog asking which
            // app to use.
            if (wouldLaunchResolverActivity(resolved, appList)) {
                // If only one of the results is a system app then choose that as the default.
                final ResolveInfo systemApp = getSingleSystemActivity(appList);
                if (systemApp == null) {
                    // There is no logical choice for this meta-favorite, so rather than making
                    // a bad choice just add nothing.
                    Log.w(TAG, "No preference or single system activity found for "
                            + metaIntent.toString());
                    return -1;
                }
                resolved = systemApp;
            }
            final ActivityInfo info = resolved.activityInfo;
            final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
            if (intent == null) {
                return -1;
            }
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

            return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
                    Favorites.ITEM_TYPE_APPLICATION);
        }

AutoInstallsLayout的addShortcut()方法

java 复制代码
    protected int addShortcut(String title, Intent intent, int type) {
        int id = mCallback.generateNewItemId();
        mValues.put(Favorites.INTENT, intent.toUri(0));
        mValues.put(Favorites.TITLE, title);
        mValues.put(Favorites.ITEM_TYPE, type);
        mValues.put(Favorites.SPANX, 1);
        mValues.put(Favorites.SPANY, 1);
        mValues.put(Favorites._ID, id);
        //添加到数据库
        if (mCallback.insertAndCheck(mDb, mValues) < 0) {
            return -1;
        } else {
            return id;
        }
    }

这里的mCallback是DatabaseHelper,它实现了LayoutParserCallback接口,在构建DefaultLayoutParser对象时,会把它传进去

DatabaseHelper

java 复制代码
        //LauncherSettings
        public static final String TABLE_NAME = "favorites";
        
        @Override
        public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
            //这里传入了表名参数
            return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
        }

LauncherProvider

java 复制代码
@Thunk static int dbInsertAndCheck(DatabaseHelper helper,
        SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
    if (values == null) {
        throw new RuntimeException("Error: attempting to insert null values");
    }
    if (!values.containsKey(LauncherSettings.Favorites._ID)) {
        throw new RuntimeException("Error: attempting to add item without specifying an id");
    }
    helper.checkId(values);
    //把数据插入到表中
    return (int) db.insert(table, nullColumnHack, values);
}

DatabaseHelper继承NoLocaleSQLiteHelper

在onCreate()方法会添加favorites表

java 复制代码
        @Override
        public void onCreate(SQLiteDatabase db) {
            if (LOGD) Log.d(TAG, "creating new launcher database");

            mMaxItemId = 1;

            addFavoritesTable(db, false);

            // Fresh and clean launcher DB.
            mMaxItemId = initializeMaxItemId(db);
            if (!mForMigration) {
                onEmptyDbCreated();
            }
        }

launcher.db数据库favorites表中数据

phone的intent数据

java 复制代码
#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;package=com.android.dialer;component=com.android.dialer/.main.impl.MainActivity;end
相关推荐
爱数学的程序猿2 分钟前
Python入门:6.深入解析Python中的序列
android·服务器·python
brhhh_sehe22 分钟前
重生之我在异世界学编程之C语言:深入文件操作篇(下)
android·c语言·网络
zhangphil26 分钟前
Android基于Path的addRoundRect,Canvas剪切clipPath简洁的圆形图实现,Kotlin(2)
android·kotlin
Calvin8808281 小时前
Android Studio 的革命性更新:Project Quartz 和 Gemini,开启 AI 开发新时代!
android·人工智能·android studio
敲代码敲到头发茂密2 小时前
【大语言模型】LangChain 核心模块介绍(Memorys)
android·语言模型·langchain
H1003 小时前
重构(二)
android·重构
拓端研究室3 小时前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil4 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
m0_512744644 小时前
极客大挑战2024-web-wp(详细)
android·前端
lw向北.5 小时前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt