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
相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui