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