1.简介
launcher的数据如何获取的
1.1.备注
这里记录下一些常用的类型id,方便查看数据库的时候知道代表啥
>itemtype:数据类型
- 0:application,应用图标
- 2:foler,文件夹
- 4:appwidget,小部件
- 6:deep shortcut,就是应用的快捷方式,长按应用图标,弹框里显示的,可以拖动到桌面显示的
/**
* The gesture is a package
*/
public static final int ITEM_TYPE_NON_ACTIONABLE = -1;
/**
* The gesture is an application
*/
public static final int ITEM_TYPE_APPLICATION = 0;
/**
* The gesture is an application created shortcut
*/
public static final int ITEM_TYPE_SHORTCUT = 1;
/**
* The favorite is a user created folder
*/
public static final int ITEM_TYPE_FOLDER = 2;
/**
* The favorite is a widget
*/
public static final int ITEM_TYPE_APPWIDGET = 4;
/**
* The favorite is a custom widget provided by the launcher
*/
public static final int ITEM_TYPE_CUSTOM_APPWIDGET = 5;
/**
* The gesture is an application created deep shortcut
*/
public static final int ITEM_TYPE_DEEP_SHORTCUT = 6;
// *** Below enum values are used for metrics purpose but not used in Favorites DB ***
/**
* Type of the item is recents task.
*/
public static final int ITEM_TYPE_TASK = 7;
/**
* The item is QSB
*/
public static final int ITEM_TYPE_QSB = 8;
/**
* The favorite is a search action
*/
public static final int ITEM_TYPE_SEARCH_ACTION = 9;
>container id,容器id
public static final int CONTAINER_DESKTOP = -100;
public static final int CONTAINER_HOTSEAT = -101;
public static final int CONTAINER_PREDICTION = -102;
public static final int CONTAINER_WIDGETS_PREDICTION = -111;
public static final int CONTAINER_HOTSEAT_PREDICTION = -103;
public static final int CONTAINER_ALL_APPS = -104;
public static final int CONTAINER_WIDGETS_TRAY = -105;
public static final int CONTAINER_BOTTOM_WIDGETS_TRAY = -112;
public static final int CONTAINER_PIN_WIDGETS = -113;
public static final int CONTAINER_WALLPAPERS = -114;
public static final int CONTAINER_SHORTCUTS = -107;
public static final int CONTAINER_SETTINGS = -108;
public static final int CONTAINER_TASKSWITCHER = -109;
// Represents any of the extended containers implemented in non-AOSP variants.
public static final int EXTENDED_CONTAINERS = -200;
>举例
如下数据库:
- 前4条数据,container是-101表示是hotseat数据
- 第五条数据itemType是2,表示是个文件夹,container是-100,表示显示在桌面
- 6到15条数据的container是5,不在我们上边列出的静态变量里,那么这个5表示的就是第五条数据的id了,他们都是放在文件夹里的
- 16,17条数据,就是普通的应用图标,显示在桌面
- 18,19是settings的快捷方式,itemType是6
- 20是个小部件,itemType是4,
2.LauncherModel.java
数据加载逻辑就在这个类里,小节4的LauncherAppState是个单例模式,而我们这个类在其构造方法里实例化了一个对象
2.0.构造方法
小节4的构造方法里实例化对象
LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
@NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
final boolean isPrimaryInstance) {
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
isPrimaryInstance);
}
2.1.addCallbacksAndLoad
添加回调并加载数据,数据请求就从这里开始
public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
synchronized (mLock) {
addCallbacks(callbacks);
//见2.2
return startLoader(new Callbacks[] { callbacks });
}
}
调用的地方有如下两处:
>Launcher.java
桌面启动的时候,onCreate方法里
protected void onCreate(Bundle savedInstanceState) {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel = app.getModel();
//..
if (!mModel.addCallbacksAndLoad(this)) {
//..
}
setContentView(getRootView());
>TaskbarViewController.java
Taskbar初始化的时候
public void init(TaskbarControllers controllers) {
//...
if (mActivity.isUserSetupComplete()) {
// Only load the callbacks if user setup is completed
LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
}
2.2.startLoader
private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
ItemInstallQueue.INSTANCE.get(mApp.getContext())
.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
synchronized (mLock) {
//stop旧的loader
boolean wasRunning = stopLoader();
//数据加载完成
boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
if (callbacksList.length > 0) {
//先清除callback的bind意图
for (Callbacks cb : callbacksList) {
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
LoaderResults loaderResults = new LoaderResults(
mApp, mBgDataModel, mBgAllAppsList, callbacksList);
if (bindDirectly) {
//走到这里说明数据已经记载完成,直接用,具体逻辑见小节5对应的方法
loaderResults.bindWorkspace(bindAllCallbacks);.
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
stopLoader();
//数据的获取都在这个task里,见小节3的run方法
mLoaderTask = new LoaderTask(
mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
//执行新的task
MODEL_EXECUTOR.post(mLoaderTask);
}
}
}
return false;
}
2.3.enqueueModelUpdateTask
把task加入队列,会先执行init方法
public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) {
if (mModelDestroyed) {
return;
}
task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
MODEL_EXECUTOR.execute(task);
}
2.4.onBroadcastIntent
小节4里注册的广播,这里处理下Local的改变,强制重新获取最新的数据。
public void onBroadcastIntent(@NonNull final Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
}
2.5.forceReload
强制重新获取数据
public void forceReload() {
synchronized (mLock) {
// Stop any existing loaders first, so they don't set mModelLoaded to true later
stopLoader();
mModelLoaded = false;
}
// Start the loader if launcher is already running, otherwise the loader will run,
// the next time launcher starts
if (hasCallbacks()) {
startLoader();
}
}
3.LoaderTask
就是个Runnable,所以核心就是run方法了
public class LoaderTask implements Runnable {
3.1.构造方法
public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
ModelDelegate modelDelegate, LoaderResults results) {
mApp = app;
mBgAllAppsList = bgAllAppsList;
mBgDataModel = dataModel;
mModelDelegate = modelDelegate;
mResults = results;
mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
mUserManager = mApp.getContext().getSystemService(UserManager.class);
mUserCache = UserCache.INSTANCE.get(mApp.getContext());
mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
mIconCache = mApp.getIconCache();
}
3.2.run
public void run() {
synchronized (this) {
// Skip fast if we are already stopped.
if (mStopped) {
return;
}
}
//见2.2
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
try { //加载workspace数据,见3.3
loadWorkspace(allShortcuts, memoryLogger);
}
if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
//清理一些无用的数据
sanitizeData();
}
//workSpace数据加载完成,绑定数据,见5.1
mResults.bindWorkspace(true /* incrementBindId */);
//见小节6.2
mModelDelegate.workspaceLoadComplete();
// Notify the installer packages of packages with active installs on the first screen.
sendFirstScreenActiveInstallsBroadcast();
// second step
// 第二步:获取allApps数据,绑定数据,加载图标
List<LauncherActivityInfo> allActivityList;
try {
allActivityList = loadAllApps();//见3.4
}
mResults.bindAllApps();//见5.2
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
setIgnorePackages(updateHandler);
//更新列表里app的图标
updateHandler.updateIcons(allActivityList,
LauncherActivityCachingLogic.newInstance(mApp.getContext()),
mApp.getModel()::onPackageIconsUpdated);
//更新shortcut的图标,集合的数据是在loadWorkSpace里获取的
updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
mApp.getModel()::onPackageIconsUpdated);
// third step
//第三步: 加载shortcuts数据,绑定数据,更新图标
List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();//见补充代码
//最终交给对应的callback处理
mResults.bindDeepShortcuts();
updateHandler.updateIcons(allDeepShortcuts,
new ShortcutCachingLogic(), (pkgs, user) -> { });
// fourth step
//第四步:加载widget数据,绑定数据,更新图标
List<ComponentWithLabelAndIcon> allWidgetsList =
mBgDataModel.widgetsModel.update(mApp, null);
mResults.bindWidgets();
updateHandler.updateIcons(allWidgetsList,
new ComponentWithIconCachingLogic(mApp.getContext(), true),
mApp.getModel()::onWidgetLabelsUpdated);
// fifth step
//第五步:加载文件夹数据
loadFolderNames();
//见小节8对应的方法
updateHandler.finish();
mModelDelegate.modelLoadComplete();//空实现
transaction.commit();//修改数据加载完成的标志
}
}
>sendFirstScreenActiveInstallsBroadcast
- 好像没有地方监听这个广播
private void sendFirstScreenActiveInstallsBroadcast() {
ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
// Screen set is never empty
IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
//获取首屏id
final int firstScreen = allScreens.get(0);
IntSet firstScreens = IntSet.wrap(firstScreen);
//把allItems数据分组,显示在首屏的放到firstScreenItems里,
filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
new ArrayList<>() /* otherScreenItems are ignored */);
mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
}
3.3.loadWorkspace
这个是核心方法,获取数据,具体分析见之前的帖子
- LauncherSettings.Settings.call执行的操作,最终在小节9里实现,可以搜索方法名找到对应的操作
- 这里用到的Uri是是 : "content://com.android.launcher3.settings/favorites"
private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger logger) {
loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
null /* selection */, logger);
}
protected void loadWorkspace(
List<ShortcutInfo> allDeepShortcuts,
Uri contentUri,
String selection,
@Nullable LoaderMemoryLogger logger) {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSafeMode = pmHelper.isSafeMode();
final boolean isSdCardReady = Utilities.isBootCompleted();
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
boolean clearDb = false;
if (!GridSizeMigrationUtil.migrateGridIfNeeded(context)) {
// Migration failed. Clear workspace.
clearDb = true;
}
if (clearDb) {
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
}
//具体逻辑见[之前的帖子]的2.2小节
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
synchronized (mBgDataModel) {
mBgDataModel.clear();
mPendingPackages.clear();
//获取正在安装中的包信息
final HashMap<PackageUserKey, SessionInfo> installingPkgs =
mSessionHelper.getActiveSessions();
installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
//根据Uri获取cursor
final LoaderCursor c = new LoaderCursor(
contentResolver.query(contentUri, null, selection, null, null), contentUri,
mApp, mUserManagerState);
final Bundle extras = c.getExtras();
mDbName = extras == null
? null : extras.getString(LauncherSettings.Settings.EXTRA_DB_NAME);
try {
final int appWidgetIdIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_ID);
final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_PROVIDER);
final int spanXIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.SPANX);
final int spanYIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.SPANY);
final int rankIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.RANK);
final int optionsIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.OPTIONS);
final int sourceContainerIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_SOURCE);
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
mUserManagerState.init(mUserCache, mUserManager);
for (UserHandle user : mUserCache.getUserProfiles()) {
long serialNo = mUserCache.getSerialNumberForUser(user);
boolean userUnlocked = mUserManager.isUserUnlocked(user);
// We can only query for shortcuts when the user is unlocked.
if (userUnlocked) {
//查找所有pinned状态的快捷方式存入map,后续用
//pinned的就是已经在桌面上的,
QueryResult pinnedShortcuts = new ShortcutRequest(context, user)
.query(ShortcutRequest.PINNED);
if (pinnedShortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : pinnedShortcuts) {
shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
shortcut);
}
} else {
// Shortcut manager can fail due to some race condition when the
// lock state changes too frequently. For the purpose of the loading
// shortcuts, consider the user is still locked.
userUnlocked = false;
}
}
//记录对应的用户是否已经unlock,只有unlock的才处理数据
unlockedUsers.put(serialNo, userUnlocked);
}
WorkspaceItemInfo info;
LauncherAppWidgetInfo appWidgetInfo;
LauncherAppWidgetProviderInfo widgetProviderInfo;
Intent intent;
String targetPkg;
List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
//根据数据库的cursor,依次读取处理数据
while (!mStopped && c.moveToNext()) {
try {
if (c.user == null) {
// User has been deleted, remove the item.
c.markDeleted("User has been deleted");
continue;
}
boolean allowMissingTarget = false;
//下边就是根据itemType封装不同的info
switch (c.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
//下边就是解析数据,判断是否有效,最终拿到info
intent = c.parseIntent();
if (intent == null) {
c.markDeleted("Invalid or null intent");
continue;
}
int disabledState = mUserManagerState.isUserQuiet(c.serialNumber)
? WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : 0;
ComponentName cn = intent.getComponent();
targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
if (TextUtils.isEmpty(targetPkg) &&
c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
c.markDeleted("Only legacy shortcuts can have null package");
continue;
}
// If there is no target package, its an implicit intent
// (legacy shortcut) which is always valid
boolean validTarget = TextUtils.isEmpty(targetPkg) ||
mLauncherApps.isPackageEnabled(targetPkg, c.user);
// If it's a deep shortcut, we'll use pinned shortcuts to restore it
if (cn != null && validTarget && c.itemType
!= LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
// If the apk is present and the shortcut points to a specific
// component.
// If the component is already present
if (mLauncherApps.isActivityEnabled(cn, c.user)) {
// no special handling necessary for this item
c.markRestored();
} else {
// Gracefully try to find a fallback activity.
intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
if (intent != null) {
c.restoreFlag = 0;
c.updater().put(
LauncherSettings.Favorites.INTENT,
intent.toUri(0)).commit();
cn = intent.getComponent();
} else {
c.markDeleted("Unable to find a launch target");
continue;
}
}
}
// else if cn == null => can't infer much, leave it
// else if !validPkg => could be restored icon or missing sd-card
if (!TextUtils.isEmpty(targetPkg) && !validTarget) {
// Points to a valid app (superset of cn != null) but the apk
// is not available.
if (c.restoreFlag != 0) {
// Package is not yet available but might be
// installed later.
FileLog.d(TAG, "package not yet restored: " + targetPkg);
tempPackageKey.update(targetPkg, c.user);
if (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) {
// Restore has started once.
} else if (installingPkgs.containsKey(tempPackageKey)) {
// App restore has started. Update the flag
c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED;
c.updater().put(LauncherSettings.Favorites.RESTORED,
c.restoreFlag).commit();
} else {
c.markDeleted("Unrestored app removed: " + targetPkg);
continue;
}
} else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
// Package is present but not available.
disabledState |= WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE;
// Add the icon on the workspace anyway.
allowMissingTarget = true;
} else if (!isSdCardReady) {
// SdCard is not ready yet. Package might get available,
// once it is ready.
Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
mPendingPackages.add(new PackageUserKey(targetPkg, c.user));
// Add the icon on the workspace anyway.
allowMissingTarget = true;
} else {
// Do not wait for external media load anymore.
c.markDeleted("Invalid package removed: " + targetPkg);
continue;
}
}
if ((c.restoreFlag & WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI) != 0) {
validTarget = false;
}
if (validTarget) {
// The shortcut points to a valid target (either no target
// or something which is ready to be used)
c.markRestored();
}
boolean useLowResIcon = !c.isOnWorkspaceOrHotseat();
if (c.restoreFlag != 0) {
// Already verified above that user is same as default user
info = c.getRestoredItemInfo(intent);
} else if (c.itemType ==
LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
info = c.getAppShortcutInfo(
intent,
allowMissingTarget,
useLowResIcon,
//最后一个参数loadicon是false,所以这里不加载图标和title
!FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get());
} else if (c.itemType ==
LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
//上边记录的用户unlock状态,为true才处理数据
if (unlockedUsers.get(c.serialNumber)) {
//这个map也是前边unlock状态的时候存入的,
ShortcutInfo pinnedShortcut =
shortcutKeyToPinnedShortcuts.get(key);
if (pinnedShortcut == null) {
// The shortcut is no longer valid.
c.markDeleted("Pinned shortcut not found");
continue;
}
info = new WorkspaceItemInfo(pinnedShortcut, context);
// If the pinned deep shortcut is no longer published,
// use the last saved icon instead of the default.
mIconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon);
if (pmHelper.isAppSuspended(
pinnedShortcut.getPackage(), info.user)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
}
intent = info.getIntent();
allDeepShortcuts.add(pinnedShortcut);
} else {
// Create a shortcut info in disabled mode for now.
info = c.loadSimpleWorkspaceItem();
info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
}
} else { // item type == ITEM_TYPE_SHORTCUT
//最新的代码已经移除这种type了,所以不看了。
info = c.loadSimpleWorkspaceItem();
// Shortcuts are only available on the primary profile
if (!TextUtils.isEmpty(targetPkg)
&& pmHelper.isAppSuspended(targetPkg, c.user)) {
disabledState |= FLAG_DISABLED_SUSPENDED;
}
info.options = c.getInt(optionsIndex);
// App shortcuts that used to be automatically added to Launcher
// didn't always have the correct intent flags set, so do that
// here
if (intent.getAction() != null &&
intent.getCategories() != null &&
intent.getAction().equals(Intent.ACTION_MAIN) &&
intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
}
if (info != null) {
if (info.itemType
!= LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
// Skip deep shortcuts; their title and icons have already been
// loaded above.
//图标的请求加入集合,后续一起处理
iconRequestInfos.add(
c.createIconRequestInfo(info, useLowResIcon));
}
c.applyCommonProperties(info);
info.intent = intent;
info.rank = c.getInt(rankIndex);
info.spanX = 1;
info.spanY = 1;
info.runtimeStatusFlags |= disabledState;
if (isSafeMode && !isSystemApp(context, intent)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
}
LauncherActivityInfo activityInfo = c.getLauncherActivityInfo();
if (activityInfo != null) {
info.setProgressLevel(
PackageManagerHelper
.getLoadingProgress(activityInfo),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
}
if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
tempPackageKey.update(targetPkg, c.user);
SessionInfo si = installingPkgs.get(tempPackageKey);
if (si == null) {
info.runtimeStatusFlags &=
~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
} else if (activityInfo == null) {
int installProgress = (int) (si.getProgress() * 100);
info.setProgressLevel(
installProgress,
PackageInstallInfo.STATUS_INSTALLING);
}
}
//数据放入model,方便后续使用
c.checkAndAddItem(info, mBgDataModel, logger);
} else {
throw new RuntimeException("Unexpected null WorkspaceItemInfo");
}
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
FolderInfo folderInfo = mBgDataModel.findOrMakeFolder(c.id);
//把cursor读取的信息放入info里
c.applyCommonProperties(folderInfo);
// Do not trim the folder label, as is was set by the user.
folderInfo.title = c.getString(c.titleIndex);
folderInfo.spanX = 1;
folderInfo.spanY = 1;
folderInfo.options = c.getInt(optionsIndex);
// no special handling required for restored folders
c.markRestored();
//数据放入model
c.checkAndAddItem(folderInfo, mBgDataModel, logger);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
//小部件类型的
if (WidgetsModel.GO_DISABLE_WIDGETS) {
c.markDeleted("Only legacy shortcuts can have null package");
continue;
}
// Follow through
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
// Read all Launcher-specific widget details
boolean customWidget = c.itemType ==
LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
int appWidgetId = c.getInt(appWidgetIdIndex);
String savedProvider = c.getString(appWidgetProviderIndex);
final ComponentName component;
boolean isSearchWidget = (c.getInt(optionsIndex)
& LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0;
if (isSearchWidget) {
//如果标记的是搜索小部件的,那获取默认的搜索组件
component = QsbContainerView.getSearchComponentName(context);
if (component == null) {
c.markDeleted("Discarding SearchWidget without packagename ");
continue;
}
} else {
//否则根据提供的provider获取组件
component = ComponentName.unflattenFromString(savedProvider);
}
final boolean isIdValid = !c.hasRestoreFlag(
LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
final boolean wasProviderReady = !c.hasRestoreFlag(
LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
ComponentKey providerKey = new ComponentKey(component, c.user);
if (!mWidgetProvidersMap.containsKey(providerKey)) {
mWidgetProvidersMap.put(providerKey,
widgetHelper.findProvider(component, c.user));
}
final AppWidgetProviderInfo provider =
mWidgetProvidersMap.get(providerKey);
final boolean isProviderReady = isValidProvider(provider);
if (!isSafeMode && !customWidget &&
wasProviderReady && !isProviderReady) {
c.markDeleted(
"Deleting widget that isn't installed anymore: "
+ provider);
} else {
if (isProviderReady) {
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
provider.provider);
// The provider is available. So the widget is either
// available or not available. We do not need to track
// any future restore updates.
int status = c.restoreFlag &
~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED &
~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
if (!wasProviderReady) {
// If provider was not previously ready, update the
// status and UI flag.
// Id would be valid only if the widget restore broadcast was received.
if (isIdValid) {
status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
}
}
appWidgetInfo.restoreStatus = status;
} else {
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
component);
appWidgetInfo.restoreStatus = c.restoreFlag;
tempPackageKey.update(component.getPackageName(), c.user);
SessionInfo si =
installingPkgs.get(tempPackageKey);
Integer installProgress = si == null
? null
: (int) (si.getProgress() * 100);
if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) {
// Restore has started once.
} else if (installProgress != null) {
// App restore has started. Update the flag
appWidgetInfo.restoreStatus |=
LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
} else if (!isSafeMode) {
c.markDeleted("Unrestored widget removed: " + component);
continue;
}
appWidgetInfo.installProgress =
installProgress == null ? 0 : installProgress;
}
if (appWidgetInfo.hasRestoreFlag(
LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
appWidgetInfo.bindOptions = c.parseIntent();
}
c.applyCommonProperties(appWidgetInfo);
appWidgetInfo.spanX = c.getInt(spanXIndex);
appWidgetInfo.spanY = c.getInt(spanYIndex);
appWidgetInfo.options = c.getInt(optionsIndex);
appWidgetInfo.user = c.user;
appWidgetInfo.sourceContainer = c.getInt(sourceContainerIndex);
if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
//跨度不正常,删除
c.markDeleted("Widget has invalid size: "
+ appWidgetInfo.spanX + "x" + appWidgetInfo.spanY);
continue;
}
widgetProviderInfo =
widgetHelper.getLauncherAppWidgetInfo(appWidgetId);
if (widgetProviderInfo != null
&& (appWidgetInfo.spanX < widgetProviderInfo.minSpanX
|| appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) {
logWidgetInfo(mApp.getInvariantDeviceProfile(),
widgetProviderInfo);
}
if (!c.isOnWorkspaceOrHotseat()) {
//小部件的容器只能是workspace或者hotseat,不是的话,删除数据
c.markDeleted("Widget found where container != " +
"CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
continue;
}
if (!customWidget) {
String providerName =
appWidgetInfo.providerName.flattenToString();
if (!providerName.equals(savedProvider) ||
(appWidgetInfo.restoreStatus != c.restoreFlag)) {
c.updater()
.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
providerName)
.put(LauncherSettings.Favorites.RESTORED,
appWidgetInfo.restoreStatus)
.commit();
}
}
if (appWidgetInfo.restoreStatus !=
LauncherAppWidgetInfo.RESTORE_COMPLETED) {
appWidgetInfo.pendingItemInfo = WidgetsModel.newPendingItemInfo(
mApp.getContext(),
appWidgetInfo.providerName,
appWidgetInfo.user);
mIconCache.getTitleAndIconForApp(
appWidgetInfo.pendingItemInfo, false);
}
c.checkAndAddItem(appWidgetInfo, mBgDataModel);
}
break;
}
}
}
//这个值前边itemType是application的时候用过,为false才加载图标,为true不加载,默认为ture
if (FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get()) {
//这里统一加载图标以及title,放在app_icons.db数据库里,具体见IconCache.java类
try {
mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo :
iconRequestInfos) {
WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
}
}
} finally {
Trace.endSection();
}
}
} finally {
IOUtils.closeSilently(c);
}
// Load delegate items,见6.3,加载本地的extra数据,
mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
// Load string cache
mModelDelegate.loadStringCache(mBgDataModel.stringCache);
// Break early if we've stopped loading
if (mStopped) {
mBgDataModel.clear();
return;
}
// Remove dead items
mItemsDeleted = c.commitDeleted();
// Sort the folder items, update ranks, and make sure all preview items are high res.
FolderGridOrganizer verifier =
new FolderGridOrganizer(mApp.getInvariantDeviceProfile());
for (FolderInfo folder : mBgDataModel.folders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
verifier.setFolderInfo(folder);
int size = folder.contents.size();
// Update ranks here to ensure there are no gaps caused by removed folder items.
// Ranks are the source of truth for folder items, so cellX and cellY can be ignored
// for now. Database will be updated once user manually modifies folder.
for (int rank = 0; rank < size; ++rank) {
WorkspaceItemInfo info = folder.contents.get(rank);
info.rank = rank;
if (info.usingLowResIcon()
&& info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& verifier.isItemInPreview(info.rank)) {
mIconCache.getTitleAndIcon(info, false);
}
}
}
c.commitRestoredItems();
}
}
>pinned shortcut
长按应用图标,比如settings,弹出的菜单里可以看到wifi ,battery等快捷方式,长按就可以拖动放到桌面上,这个图标就是上边的pinned shortcut
3.4.loadAllApps
private List<LauncherActivityInfo> loadAllApps() {
final List<UserHandle> profiles = mUserCache.getUserProfiles();
List<LauncherActivityInfo> allActivityList = new ArrayList<>();
// Clear the list of apps
mBgAllAppsList.clear();
List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
for (UserHandle user : profiles) {
// 查找对应用户的app列表
final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
if (apps == null || apps.isEmpty()) {
return allActivityList;
}
boolean quietMode = mUserManagerState.isUserQuiet(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
AppInfo appInfo = new AppInfo(app, user, quietMode);
//这个是app图标加载的请求集合
iconRequestInfos.add(new IconRequestInfo<>(
appInfo, app, /* useLowResIcon= */ false));
mBgAllAppsList.add(
appInfo, app, !FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get());
}
//app列表加入集合,
allActivityList.addAll(apps);
}
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
// get all active sessions and add them to the all apps list
for (PackageInstaller.SessionInfo info :
mSessionHelper.getAllVerifiedSessions()) {
AppInfo promiseAppInfo = mBgAllAppsList.addPromiseApp(
mApp.getContext(),
PackageInstallInfo.fromInstallingState(info),
!FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get());
if (promiseAppInfo != null) {
iconRequestInfos.add(new IconRequestInfo<>(
promiseAppInfo,
/* launcherActivityInfo= */ null,
promiseAppInfo.usingLowResIcon()));
}
}
}
if (FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get()) {
try {
//请求加载图标
mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
iconRequestInfos.forEach(iconRequestInfo ->
mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo));
}
}
//设置对应的flag
mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
mUserManagerState.isAnyProfileQuietModeEnabled());
mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION,
hasShortcutsPermission(mApp.getContext()));
mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION,
mApp.getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
== PackageManager.PERMISSION_GRANTED);
mBgAllAppsList.getAndResetChangeFlag();
return allActivityList;
}
3.5.loadDeepShortcuts
private List<ShortcutInfo> loadDeepShortcuts() {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
mBgDataModel.deepShortcutMap.clear();
//先判断是否有获取shortcut的权限,3.4末尾设置的flag
if (mBgAllAppsList.hasShortcutHostPermission()) {
for (UserHandle user : mUserCache.getUserProfiles()) {
if (mUserManager.isUserUnlocked(user)) {
//查找所有的app支持的shortcuts
List<ShortcutInfo> shortcuts = new ShortcutRequest(mApp.getContext(), user)
.query(ShortcutRequest.ALL);
allShortcuts.addAll(shortcuts);
mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
}
}
}
return allShortcuts;
}
3.6.loadFolderNames
private void loadFolderNames() {
FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
mBgAllAppsList.data, mBgDataModel.folders);
synchronized (mBgDataModel) {
for (int i = 0; i < mBgDataModel.folders.size(); i++) {
FolderNameInfos suggestionInfos = new FolderNameInfos();
FolderInfo info = mBgDataModel.folders.valueAt(i);
if (info.suggestedFolderNames == null) {
provider.getSuggestedFolderName(mApp.getContext(), info.contents,
suggestionInfos);
info.suggestedFolderNames = suggestionInfos;
}
}
}
}
4.LauncherAppState.java
4.1.构造方法
public LauncherAppState(Context context) {
//监听配置改变
mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
if (modelPropertiesChanged) {
refreshAndReloadLauncher();
}
});
//注册app改变监听
mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
//注册广播,监听语言改变,见2.4
SimpleBroadcastReceiver modelChangeReceiver =
new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
mContext = context;
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
mIconProvider = new LauncherIconProvider(context);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
//这里实例化小节2的model
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
}
>单例模式
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
new MainThreadInitializedObject<>(LauncherAppState::new);
public static LauncherAppState getInstance(final Context context) {
return INSTANCE.get(context);
}
5.BaseLoaderResults.java
5.1.bindWorkspace
在ui线程里加载数据到对应的view上
public void bindWorkspace(boolean incrementBindId) {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
final IntArray orderedScreenIds = new IntArray();
//这个就是预测的items的集合,比如常用一个app,在hotseat第一个位置图标可能就变成这个app了
ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
synchronized (mBgDataModel) {
//copy相关的数据到新的集合里
workspaceItems.addAll(mBgDataModel.workspaceItems);
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
mBgDataModel.extraItems.forEach(extraItems::add);
if (incrementBindId) {
mBgDataModel.lastBindId++;
}
mMyBindingId = mBgDataModel.lastBindId;
}
//循环所有的回调
for (Callbacks cb : mCallbacksList) {
//把数据交给对应的callback处理
new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
}
}
5.2.bindAllApps
把allapps数据以及对应的flags传递给回调。
public void bindAllApps() {
// shallow copy
AppInfo[] apps = mBgAllAppsList.copyData();
int flags = mBgAllAppsList.getFlags();
executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor);
}
5.3.bindDeepShortcuts
把数据给到对应的callback里处理,父类LoaderResults实现
public void bindDeepShortcuts() {
final HashMap<ComponentKey, Integer> shortcutMapCopy;
synchronized (mBgDataModel) {
shortcutMapCopy = new HashMap<>(mBgDataModel.deepShortcutMap);
}
executeCallbacksTask(c -> c.bindDeepShortcutMap(shortcutMapCopy), mUiExecutor);
}
5.4.bindWidgets
把数据给到对应的callback里处理,子类LoaderResults实现(有两个同名类,默认的空实现,override目录里实现)
public void bindWidgets() {
final List<WidgetsListBaseEntry> widgets =
mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
}
5.5.executeCallbacksTask
这个方法的作用,就是在给定的executor里执行所有callback的task里的方法
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
if (mMyBindingId != mBgDataModel.lastBindId) {
Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
return;
}
for (Callbacks cb : mCallbacksList) {
task.execute(cb);
}
});
}
6.QuickstepModelDelegate.java
父类有个静态方法实例化的,
public static ModelDelegate newInstance(
Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel,
boolean isPrimaryInstance) {
//model_delegate_class这个string里配置的类名,反射实例化的
ModelDelegate delegate = Overrides.getObject(
ModelDelegate.class, context, R.string.model_delegate_class);
delegate.init(context, app, appsList, dataModel, isPrimaryInstance);
return delegate;
}
6.1.构造方法
public QuickstepModelDelegate(Context context) {
mContext = context;
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
mStatsManager = context.getSystemService(StatsManager.class);
}
6.2.workspaceLoadComplete
public void workspaceLoadComplete() {
super.workspaceLoadComplete();
recreatePredictors();
}
6.4.recreatePredictors
重新创建新的预测器,简单理解为常用的应用,排名较高,就会推荐你使用
//-102 ,后边的参数就是本地保存的文件名,见小节7
private final PredictorState mAllAppsState =
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
//-103
private final PredictorState mHotseatState =
new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
//-111
private final PredictorState mWidgetsRecommendationState =
new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction");
private void recreatePredictors() {
destroyPredictors();
if (!mActive) {
return;
}
Context context = mApp.getContext();
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
registerPredictor(mAllAppsState, apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("home")
.setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns)
.build()));
//
registerPredictor(mHotseatState, apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("hotseat")
.setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
.setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
.build()));
registerWidgetsPredictor(apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("widgets")
.setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel))
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
.build()));
}
>destroyPredictors
private void destroyPredictors() {
mAllAppsState.destroyPredictor();
mHotseatState.destroyPredictor();
mWidgetsRecommendationState.destroyPredictor();
}
>registerPredictor
allApps,hotseat用这个,他们的update方法一样
private void registerPredictor(PredictorState state, AppPredictor predictor) {
state.predictor = predictor;
state.predictor.registerPredictionUpdates(
MODEL_EXECUTOR, t -> handleUpdate(state, t));
state.predictor.requestPredictionUpdate();
}
//都是这个update方法
private void handleUpdate(PredictorState state, List<AppTarget> targets) {
if (state.setTargets(targets)) {
//数据未发生变化,targets的个数,以及里边的包名,类名,user,shorcutinfo都得一样
return;
}
//把task加入队列,见2.3,具体的task逻辑7.1
mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
}
>registerWidgetsPredictor
小部件用的,里边的updates方法不一样,其实就是task不一样,
private void registerWidgetsPredictor(AppPredictor predictor) {
mWidgetsRecommendationState.predictor = predictor;
mWidgetsRecommendationState.predictor.registerPredictionUpdates(
MODEL_EXECUTOR, targets -> {
if (mWidgetsRecommendationState.setTargets(targets)) {
// No diff, skip
return;
}
mApp.getModel().enqueueModelUpdateTask(
//7.2
new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets));
});
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
}
6.3.loadItems
- 加载本地保存的extra数据,allapps以及hotseat,另外widgets没有本地存储,默认数据为空集合
- 文件位置见小节7,文件名见6.4
public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
super.loadItems(ums, pinnedShortcuts);
//获取allapps数据,放入extraItems
WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
mIDP.numDatabaseAllAppsColumns, mAllAppsState.containerId);
FixedContainerItems allAppsPredictionItems = new FixedContainerItems(
mAllAppsState.containerId, mAllAppsState.storage.read(mApp.getContext(),
allAppsFactory, ums.allUsers::get));
mDataModel.extraItems.put(mAllAppsState.containerId, allAppsPredictionItems);
//获取hotseat数据,放入extraItems
WorkspaceItemFactory hotseatFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
mIDP.numDatabaseHotseatIcons, mHotseatState.containerId);
FixedContainerItems hotseatItems = new FixedContainerItems(mHotseatState.containerId,
mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
mDataModel.extraItems.put(mHotseatState.containerId, hotseatItems);
//空的widgets数据,放入extraItems
// Widgets prediction isn't used frequently. And thus, it is not persisted on disk.
mDataModel.extraItems.put(mWidgetsRecommendationState.containerId,
new FixedContainerItems(mWidgetsRecommendationState.containerId,
new ArrayList<>()));
mActive = true;
}
7.BaseModelUpdateTask.java
就是个runnable
//run方法里调用抽象方法execute,子类需要实现具体的操作
public abstract class BaseModelUpdateTask implements ModelUpdateTask {
// 接口里带一个init方法,用来初始化要用到的参数
public interface ModelUpdateTask extends Runnable {
>bindUpdatedWorkspaceItems
绑定workspace数据
public void bindUpdatedWorkspaceItems(@NonNull final List<WorkspaceItemInfo> allUpdates) {
// Bind workspace items,过滤下没有id的
List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
.filter(info -> info.id != ItemInfo.NO_ID)
.collect(Collectors.toList());
if (!workspaceUpdates.isEmpty()) {
//数据传递给对应的回调
scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
}
// Bind extra items if any
allUpdates.stream()
.mapToInt(info -> info.container)
.distinct()
.mapToObj(mDataModel.extraItems::get)
.filter(Objects::nonNull)
.forEach(this::bindExtraContainerItems);
}
>bindExtraContainerItems
绑定扩展的数据,就是系统推荐的app
public void bindExtraContainerItems(@NonNull final FixedContainerItems item) {
scheduleCallbackTask(c -> c.bindExtraContainerItems(item));
}
7.1.PredictionUpdateTask.java
小节6.4里使用,prediction数据变化的监听里添加这个task
public class PredictionUpdateTask extends BaseModelUpdateTask {
>execute
public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
@NonNull final AllAppsList apps) {
Context context = app.getContext();
// TODO: remove this
LauncherPrefs.getDevicePrefs(context).edit()
.putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
//查找对应容器id的数据,并过滤对应的type,最终获取有快捷方式发生变化的所有用户
Set<UserHandle> usersForChangedShortcuts =
dataModel.extraItems.get(mPredictorState.containerId).items.stream()
.filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT)
.map(info -> info.user)
.collect(Collectors.toSet());
List<ItemInfo> items = new ArrayList<>(mTargets.size());
for (AppTarget target : mTargets) {
WorkspaceItemInfo itemInfo;
ShortcutInfo si = target.getShortcutInfo();
//打印了日志,这个si全都是空的,
if (si != null) {
usersForChangedShortcuts.add(si.getUserHandle());
//封装itemInfo并加载icon
itemInfo = new WorkspaceItemInfo(si, context);
app.getIconCache().getShortcutIcon(itemInfo, si);
} else {
String className = target.getClassName();
if (COMPONENT_CLASS_MARKER.equals(className)) {
//instant app,忽略
continue;
}
ComponentName cn = new ComponentName(target.getPackageName(), className);
UserHandle user = target.getUser();
//从apps里查找,user和组件名一样的
itemInfo = apps.data.stream()
.filter(info -> user.equals(info.user) && cn.equals(info.componentName))
.map(ai -> {
//缓存title和icon并封装为WorkspaceItemInfo
app.getIconCache().getTitleAndIcon(ai, false);
return ai.makeWorkspaceItem(context);
})
.findAny()
.orElseGet(() -> {
//这里是上边没有找到数据走这里,正常不会走这里。
LauncherActivityInfo lai = context.getSystemService(LauncherApps.class)
.resolveActivity(AppInfo.makeLaunchIntent(cn), user);
if (lai == null) {
return null;
}
AppInfo ai = new AppInfo(context, lai, user);
app.getIconCache().getTitleAndIcon(ai, lai, false);
return ai.makeWorkspaceItem(context);
});
if (itemInfo == null) {
continue;
}
}
itemInfo.container = mPredictorState.containerId;
items.add(itemInfo);
}
//更新预测的数据
FixedContainerItems fci = new FixedContainerItems(mPredictorState.containerId, items);
dataModel.extraItems.put(fci.containerId, fci);
//具体见上边父类实现,对应的回调处理数据。
bindExtraContainerItems(fci);
//更新快捷方式发生变化的用户的数据
usersForChangedShortcuts.forEach(
u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
//把items数据写入本地
mPredictorState.storage.write(context, fci.items);
}
7.2.WidgetsPredictionUpdateTask
小节6.4里使用,prediction数据变化的监听里添加这个task
public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
>execute
使用应用程序预测结果来推断用户可能想要使用的小部件
public void execute(@NonNull final LauncherAppState appState,
@NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
widget -> new ComponentKey(widget.providerName, widget.user)).collect(
Collectors.toSet());
Predicate<WidgetItem> notOnWorkspace = w -> !widgetsInWorkspace.contains(w);
Map<PackageUserKey, List<WidgetItem>> allWidgets =
dataModel.widgetsModel.getAllWidgetsWithoutShortcuts();
List<WidgetItem> servicePredictedItems = new ArrayList<>();
List<WidgetItem> localFilteredWidgets = new ArrayList<>();
for (AppTarget app : mTargets) {
PackageUserKey packageUserKey = new PackageUserKey(app.getPackageName(), app.getUser());
List<WidgetItem> widgets = allWidgets.get(packageUserKey);
if (widgets == null || widgets.isEmpty()) {
continue;
}
String className = app.getClassName();
if (!TextUtils.isEmpty(className)) {
WidgetItem item = widgets.stream()
.filter(w -> className.equals(w.componentName.getClassName()))
.filter(notOnWorkspace)
.findFirst()
.orElse(null);
if (item != null) {
servicePredictedItems.add(item);
continue;
}
}
// No widget was added by the service, try local filtering
widgets.stream().filter(notOnWorkspace).findFirst()
.ifPresent(localFilteredWidgets::add);
}
if (servicePredictedItems.isEmpty()) {
servicePredictedItems.addAll(localFilteredWidgets);
}
List<ItemInfo> items = servicePredictedItems.stream()
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION))
.collect(Collectors.toList());
FixedContainerItems fixedContainerItems =
new FixedContainerItems(mPredictorState.containerId, items);
//更新数据
dataModel.extraItems.put(mPredictorState.containerId, fixedContainerItems);
//绑定数据,见父类实现
bindExtraContainerItems(fixedContainerItems);
//widgets的prediction数据不用本地保存
// Don't store widgets prediction to disk because it is not used frequently.
}
7.3.bindExtraContainerItems
看下具体的回调都干啥了
>QuickStepLauncher里的回调
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
//找到对应的view
PredictionRowView<?> predictionRowView =
getAppsView().getFloatingHeaderView().findFixedRowByType(
PredictionRowView.class);
//设置数据
predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
//见小节11.1
mHotseatPredictionController.setPredictedItems(item);
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
getPopupDataProvider().setRecommendedWidgets(item.items);
}
}
>TaskbarModelCallbacks.java
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
mPredictedItems = item.items;
commitItemsToUI();
} else if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mControllers.taskbarAllAppsController.setPredictedApps(item.items);
}
}
7.4.图片
>CONTAINER_PREDICTION
就是allapps上边的容器
>CONTAINER_HOTSEAT_PREDICTION
红线那个带背景的就是推荐的app
>CONTAINER_WIDGETS_PREDICTION
暂时不清楚布局在哪里。
7.5.PredictState
数据存储位置:/data/user/0/com.android.launcher3/files
//文件存储位置所用的方法如下
getFileStreamPath("xxxx.xml")
>all_apps_predictions.xml
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<items>
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.chrome/com.google.android.apps.chrome.Main;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.messaging/.ui.ConversationListActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.dialer/.extensions.GoogleDialtactsActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.vending/.AssetBrowserActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.video/.VideoBrowserActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.deskclock/com.android.deskclock.DeskClock;sourceBounds=195%20470%20297%20628;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.photos/.home.HomeActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.keep/.activities.BrowseActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calculator/com.android.calculator2.Calculator;end" />
</items>
>hotseat_predictions.xml
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<items>
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.deskclock/com.android.deskclock.DeskClock;sourceBounds=195%20470%20297%20628;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.video/.VideoBrowserActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.chrome/com.google.android.apps.chrome.Main;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.messaging/.ui.ConversationListActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.dialer/.extensions.GoogleDialtactsActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.vending/.AssetBrowserActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.photos/.home.HomeActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.keep/.activities.BrowseActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calculator/com.android.calculator2.Calculator;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;end" />
<entry itemType="0" profileId="0" intent="#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.tachyon/.MainActivity;end" />
</items>
8.IconCacheUpdateHandler.java
8.1.finish
可以看到,有要删除的item才会执行db操作,没有的话其实啥也没干
public void finish() {
// Commit all deletes
int deleteCount = 0;
StringBuilder queryBuilder = new StringBuilder()
.append(IconDB.COLUMN_ROWID)
.append(" IN (");
int count = mItemsToDelete.size();
for (int i = 0; i < count; i++) {
if (mItemsToDelete.valueAt(i)) {
if (deleteCount > 0) {
queryBuilder.append(", ");
}
queryBuilder.append(mItemsToDelete.keyAt(i));
deleteCount++;
}
}
queryBuilder.append(')');
if (deleteCount > 0) {
mIconCache.mIconDb.delete(queryBuilder.toString(), null);
}
}
9.LauncherProvider.java
9.1.createEmptyDB
public void createEmptyDB(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
//删除两个表,重新create
dropTable(db, Favorites.TABLE_NAME);
dropTable(db, "workspaceScreens");
onCreate(db);
t.commit();
}
}
9.2.loadFavorites
@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, new IntArray());
// Ensure that the max ids are initialized
mMaxItemId = initializeMaxItemId(db);
return count;
}
10.LoaderCursor.java
10.1.构造方法
public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app,
UserManagerState userManagerState) {
super(cursor);
allUsers = userManagerState.allUsers;
mContentUri = contentUri;
mContext = app.getContext();
mIconCache = app.getIconCache();
mIDP = app.getInvariantDeviceProfile();
mPM = mContext.getPackageManager();
// Init column indices 获取对应列的index
iconIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
iconPackageIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
iconResourceIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
titleIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
idIndex = getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
containerIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
itemTypeIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
screenIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
cellXIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
cellYIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
profileIdIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID);
restoredIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED);
intentIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
}
11.HotseatPredictionController.java
11.1.setPredictedItems
public void setPredictedItems(FixedContainerItems items) {
boolean shouldIgnoreVisibility = FeatureFlags.ENABLE_APP_PREDICTIONS_WHILE_VISIBLE.get()
|| mLauncher.isWorkspaceLoading()
|| mPredictedItems.equals(items.items)
|| mHotseat.getShortcutsAndWidgets().getChildCount() < mHotSeatItemsCount;
if (!shouldIgnoreVisibility
&& mHotseat.isShown()
&& mHotseat.getWindowVisibility() == View.VISIBLE) {
mHotseat.setOnVisibilityAggregatedCallback((isVisible) -> {
if (isVisible) {
return;
}
mHotseat.setOnVisibilityAggregatedCallback(null);
applyPredictedItems(items);
});
} else {
mHotseat.setOnVisibilityAggregatedCallback(null);
//核心是这个
applyPredictedItems(items);
}
}
>applyPredictedItems
private void applyPredictedItems(FixedContainerItems items) {
mPredictedItems = new ArrayList(items.items);
if (mPredictedItems.isEmpty()) {
HotseatRestoreHelper.restoreBackup(mLauncher);
}
fillGapsWithPrediction();
}
11.2.fillGapsWithPrediction
- hotseat里如果普通图标已经占满,是不会显示推荐图标的
- 当有空位的时候,才会在空位那里显示推荐图标
private void fillGapsWithPrediction(boolean animate) {
if (mPauseFlags != 0) {
return;
}
int predictionIndex = 0;
int numViewsAnimated = 0;
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
// make sure predicted icon removal and filling predictions don't step on each other
//这个就是如果有旧的view移除动画正在执行,监听动画结束再开始新的fill
if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
//旧的动画结束再重新开始执行这个方法
fillGapsWithPrediction(animate);
mIconRemoveAnimators.removeListener(this);
}
});
//有旧的动画,返回,监听动画结束
return;
}
//修改flag,防止多次进入
mPauseFlags |= FLAG_FILL_IN_PROGRESS;
//从0开始循环hotseat
for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
//查找对应位置的child
View child = mHotseat.getChildAt(
mHotseat.getCellXFromOrder(rank),
mHotseat.getCellYFromOrder(rank));
if (child != null && !isPredictedIcon(child)) {
//对应位置已有非推荐的图标,也就是普通的hotseat
continue;
}
//predictionIndex后边查找数据的时候会自增
if (mPredictedItems.size() <= predictionIndex) {
//predictedItems 数据已经读取完了,后边还有predicted类型的child,移除
// Remove predicted apps from the past
if (isPredictedIcon(child)) {
mHotseat.removeView(child);
}
continue;
}
WorkspaceItemInfo predictedItem =
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
走到这里说明,child为null或者是predictedIcon
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
//更新child的数据为新的
icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
if (animateIconChange) {
numViewsAnimated++;
}
icon.finishBinding(mPredictionLongClickListener);
} else {
//这里说明child为null
newItems.add(predictedItem);
}
//更新数据
preparePredictionInfo(predictedItem, rank);
}
//有新的item需要添加
bindItems(newItems, animate);
mPauseFlags &= ~FLAG_FILL_IN_PROGRESS;
}
>isPredictedIcon
判断旧的view是否是predictedIcon
private static boolean isPredictedIcon(View view) {
return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) view.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
>preparePredictionInfo
private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
itemInfo.rank = rank;
itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
itemInfo.screenId = rank;
}
>bindItems
private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate) {
AnimatorSet animationSet = new AnimatorSet();
for (WorkspaceItemInfo item : itemsToAdd) {
//创建新的icon
PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
//绑定到hotseat里
mLauncher.getWorkspace().addInScreenFromBind(icon, item);
icon.finishBinding(mPredictionLongClickListener);
if (animate) {
animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
}
}
if (animate) {
animationSet.addListener(
forSuccessCallback(this::removeOutlineDrawings));
animationSet.start();
} else {
removeOutlineDrawings();
}
}