There are four ways to load layout resources in Launcher’s workspace. The loading entrance is in LauncherProvider.java. Understanding these four loading methods will be very helpful for us to develop launcher, customize workspace, and share direct resources between two applications.
Create a ContentProvider when the launcher starts, and then call the call method
packages\apps\Launcher3\src\com\android\launcher3\LauncherProvider.java public Bundle call(String method, final String arg, final Bundle extras) { if (Binder.getCallingUid() != Process.myUid()) { return null; } createDbIfNotExists();//Create DB switch (method) { ............ case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: { loadDefaultFavoritesIfNecessary(); return null; } ..................... } return null; }
packages\apps\Launcher3\src\com\android\launcher3\LauncherProvider.java synchronized private void loadDefaultFavoritesIfNecessary() { if (getFlagEmptyDbCreated(getContext(), mOpenHelper.getDatabaseName())) {//Read whether the database is empty from sharepreferences Log.d(TAG, "loading default workspace"); AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost(); AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);//The first way to customize workspace if (loader == null) { loader = AutoInstallsLayout.get(getContext(), widgetHost, mOpenHelper);//The second method of customizing workspace } if (loader == null) {//The third method of customizing workspace final Partner partner = Partner.get(getContext().getPackageManager()); if (partner != null & amp; & amp; partner.hasDefaultLayout()) { final Resources partnerRes = partner.getResources(); int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, "xml", partner.getPackageName()); if (workspaceResId != 0) { loader = new DefaultLayoutParser(getContext(), widgetHost, mOpenHelper, partnerRes, workspaceResId); } } } final boolean usingExternallyProvidedLayout = loader != null; if (loader == null) {//The fourth method of customizing workspace loader = getDefaultLayoutParser(widgetHost); } ............. } }
There are four ways for workspace to load layout resources. Starting from the first method, the resources are obtained in sequence. If the resources cannot be obtained, the next one is obtained.
The first way to customize workspace
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) { Context ctx = getContext(); InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx); String authority = Settings.Secure.getString(ctx.getContentResolver(), "launcher3.layout.provider");//Get the authority part of contentprovider by reading settings if (TextUtils.isEmpty(authority)) { return null; } ProviderInfo pi = ctx.getPackageManager().resolveContentProvider(authority, 0); if (pi == null) { Log.e(TAG, "No provider found for authority " + authority); return null; } Uri uri = new Uri.Builder().scheme("content").authority(authority).path("launcher_layout") .appendQueryParameter("version", "1") .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns)) .appendQueryParameter("gridHeight", Integer.toString(grid.numRows)) .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons)) .build();//Create URI based on the combination of authority, number of rows and columns, etc. try (InputStream in = ctx.getContentResolver().openInputStream(uri)) {//Read contentprovider information through the stream // Read the full xml so that we fail early in case of any IO error. String layout = new String(IOUtils.toByteArray(in)); XmlPullParser parser = Xml.newPullParser(); parser.setInput(new StringReader(layout));//Create a string stream and parse it with pull Log.d(TAG, "Loading layout from " + authority); return new AutoInstallsLayout(ctx, widgetHost, mOpenHelper, ctx.getPackageManager().getResourcesForApplication(pi.applicationInfo), () -> parser, AutoInstallsLayout.TAG_WORKSPACE); } catch (Exception e) { Log.e(TAG, "Error getting layout stream from: " + authority , e); return null; } }
The second method of customizing workspace
static final String ACTION_LAUNCHER_CUSTOMIZATION = "android.autoinstalls.config.action.PLAY_AUTO_INSTALL"; 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";
packages\apps\Launcher3\src\com\android\launcher3\AutoInstallsLayout.java static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback) { Pair<String, Resources> customizationApkInfo = Utilities.findSystemApk( ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager()); if (customizationApkInfo == null) { return null; } String pkg = customizationApkInfo.first; Resources targetRes = customizationApkInfo.second; InvariantDeviceProfile grid = LauncherAppState.getIDP(context); String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT, grid.numColumns, grid.numRows, grid.numHotseatIcons);//The file name is combined by the rows and hotseats of the device, that is, default_layout_%dx%d_h%s int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);//Get the id of default_layout_%dx%d_h%s.xml from the pkg package // Try with only grid size if (layoutId == 0) {//Failed to obtain the id of default_layout_%dx%d_h%s.xml Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying layout without hosteat"); layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES, grid.numColumns, grid.numRows);//The file name is formed by combining the rows and columns of the device, that is, default_layout_%dx%d layoutId = targetRes.getIdentifier(layoutName, "xml", pkg); } // Try the default layout if (layoutId == 0) {//Failed to obtain the id of default_layout_%dx%d.xml Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout"); layoutId = targetRes.getIdentifier(LAYOUT_RES, "xml", pkg);//Get the id of default_layout.xml from the pkg package } if (layoutId == 0) { Log.e(TAG, "Layout definition not found in package: " + pkg); return null; } return new AutoInstallsLayout(context, appWidgetHost, callback, targetRes, layoutId, TAG_WORKSPACE); }
static Pair<String, Resources> findSystemApk(String action, PackageManager pm) { final Intent intent = new Intent(action);//action is android.autoinstalls.config.action.PLAY_AUTO_INSTALL for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {//Find the broadcast containing action in the system if (info.activityInfo != null & amp; & amp; (info.activityInfo.applicationInfo.flags & amp; ApplicationInfo.FLAG_SYSTEM) != 0) {//The action package must be a system application final String packageName = info.activityInfo.packageName; try { final Resources res = pm.getResourcesForApplication(packageName);//Get the resource object through the package name return Pair.create(packageName, res);//Return package name and resource object } catch (NameNotFoundException e) { Log.w(TAG, "Failed to find resources for " + packageName); } } } return null; }
In the second method of customizing the workspace, first search for the broadcast of the system application containing the action android.autoinstalls.config.action.PLAY_AUTO_INSTALL, get the package name and resource object of this broadcast, and then use the package name and resource object to obtain them respectively. Specify the number of rows and columns, hotseat, number of rows and columns, and the xml file of default_layout that is not specified. If obtained, use pull to parse it.
The third way to customize workspace
public static final String RES_DEFAULT_LAYOUT = "partner_default_layout"; synchronized private void loadDefaultFavoritesIfNecessary() { ..................... if (loader == null) { final Partner partner = Partner.get(getContext().getPackageManager()); if (partner != null & amp; & amp; partner.hasDefaultLayout()) { final Resources partnerRes = partner.getResources(); int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, "xml", partner.getPackageName());//Get the resource id of partner_default_layout.xml if (workspaceResId != 0) { loader = new DefaultLayoutParser(getContext(), widgetHost, mOpenHelper, partnerRes, workspaceResId); } } } ............................. }
private static final String ACTION_PARTNER_CUSTOMIZATION = "com.android.launcher3.action.PARTNER_CUSTOMIZATION"; public static synchronized Partner get(PackageManager pm) { if (!sSearched) { Pair<String, Resources> apkInfo = Utilities.findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);//Get the application whose system application broadcast action is com.android.launcher3.action.PARTNER_CUSTOMIZATION if (apkInfo != null) { sPartner = new Partner(apkInfo.first, apkInfo.second); } sSearched = true; } return sPartner; }
The third method is similar to the second method. After obtaining the application package name and resources containing the specified broadcast, directly obtain the id of partner_default_layout.xml, and then parse it through pull.
The fourth way to customize workspace
private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) { InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext()); int defaultLayout = idp.defaultLayoutId; UserManagerCompat um = UserManagerCompat.getInstance(getContext()); if (um.isDemoUser() & amp; & idp.demoModeLayoutId != 0) { defaultLayout = idp.demoModeLayoutId; } return new DefaultLayoutParser(getContext(), widgetHost, mOpenHelper, getContext().getResources(), defaultLayout); }
The fourth way is to directly obtain the xml file under Launcher
Summary: The first way for Launcher’s workspace to load layout resources is to read the file stream through ContetProver; the second and third ways are to obtain the package name by querying the system application’s specified broadcast action, and then obtain the package name through the package name The specified xml file; the fourth method is to directly parse the default_workspace_xxx.xml in the Launcher directory. When we develop applications, we can use the first, second, and third ideas to realize resource sharing between the two applications and develop functions with special needs.