1.简介
1.1.问题
根据已有的知识,应用添加小组件需要注册一个带固定meta-data数据以及IntentFilter的广播,如下
<receiver android:name=".ProtipWidget"
android:label="@string/widget_name"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/widget_build"/> //见补充1
</receiver>
可我在settings的清单文件里没搜到对应的intent,那么为啥settings还会显示一个小组件?
1.2.总结
- settings利用的是"android.intent.action.CREATE_SHORTCUT",launcher里会查找这种activity
- 参考2.1获取数据,具体查找CREATE_SHORTCUT的是小节2.2.1的方法
- 参考小节4.4小部件添加的方式,点击后跳转的是小节5的对象,参考5.1显示,后边就是拖拽到桌面,松手后会执行4.3的方法
- 参考4.3可以看到跳转到CREATE_SHORTCUT对应的页面,页面数据参考小节3
- 小节3选择一个快捷方式后点击,回到小节4.1添加数据,到此结束
2.WidgetsModel.java
- BgDataModel是单例模式,里边有创建这个类的对象,launcher里数据加载流程这里不重复了
- 数据调用参考小节4.3.1末尾的refreshAndBindWidgetsForPackageUser方法
- 里边会先调用2.1的方法刷新数据,完事再调用2.3获取数据进行绑定
2.1.update
import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
public List<ComponentWithLabelAndIcon> update(
LauncherAppState app, @Nullable PackageUserKey packageUser) {
Preconditions.assertWorkerThread();
Context context = app.getContext();
final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
List<ComponentWithLabelAndIcon> updatedItems = new ArrayList<>();
try {
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
PackageManager pm = app.getContext().getPackageManager();
// Widgets,这个就是正常的小组件,就是广播注册的那个
WidgetManagerHelper widgetManager = new WidgetManagerHelper(context);
for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
LauncherAppWidgetProviderInfo launcherWidgetInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
widgetsAndShortcuts.add(new WidgetItem(//参考2.4.1
launcherWidgetInfo, idp, app.getIconCache()));
updatedItems.add(launcherWidgetInfo);
}
// Shortcuts
for (ShortcutConfigActivityInfo info :
queryList(context, packageUser)) {//方法参考2.2.1
//对象参考2.4.2,这里是添加的是快捷方式配置页面
widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
updatedItems.add(info);
}
//最后处理数据
setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
}
>1.setWidgetsAndShortcuts
private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
LauncherAppState app, @Nullable PackageUserKey packageUser) {
PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
if (packageUser == null) {
// Clear the list if this is an update on all widgets and shortcuts.
mWidgetsList.clear();
} else {
// Otherwise, only clear the widgets and shortcuts for the changed package.
mWidgetsList.remove(packageItemInfoCache.getOrCreate(packageUser));
}
//添加数据,更新数据
mWidgetsList.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
.map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
.collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));
// Update each package entry
IconCache iconCache = app.getIconCache();
for (PackageItemInfo p : packageItemInfoCache.values()) {
iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
}
}
2.2.ShortcutConfigActivityInfo
>1.queryList
public static List<ShortcutConfigActivityInfo> queryList(
Context context, @Nullable PackageUserKey packageUser) {
List<ShortcutConfigActivityInfo> result = new ArrayList<>();
UserHandle myUser = Process.myUserHandle();
final List<UserHandle> users;
final String packageName;
if (packageUser == null) {
users = UserCache.INSTANCE.get(context).getUserProfiles();
packageName = null;
} else {
users = Collections.singletonList(packageUser.mUser);
packageName = packageUser.mPackageName;
}
LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
for (UserHandle user : users) {
boolean ignoreTargetSdk = myUser.equals(user);
//就是查找"android.intent.action.CREATE_SHORTCUT"这种IntentFilter的activity
//参考3.1
for (LauncherActivityInfo activityInfo :
launcherApps.getShortcutConfigActivityList(packageName, user)) {
if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.O) {
result.add(new ShortcutConfigActivityInfoVO(activityInfo));
}
}
}
return result;
}
}
2.3.getWidgetsListForPicker
public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
//数据来源是2.1.1
for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
PackageItemInfo pkgItem = entry.getKey();
List<WidgetItem> widgetItems = entry.getValue();
String sectionName = (pkgItem.title == null) ? "" :
indexer.computeSectionName(pkgItem.title);
result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItems));
result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems));
}
return result;
}
2.4.WidgetItem
>1.构造方法1
这种是小组件
public WidgetItem(LauncherAppWidgetProviderInfo info,
InvariantDeviceProfile idp, IconCache iconCache) {
super(info.provider, info.getProfile());
label = iconCache.getTitleNoCache(info);
widgetInfo = info;//有小组件信息
activityInfo = null;//没有activity信息
spanX = Math.min(info.spanX, idp.numColumns);
spanY = Math.min(info.spanY, idp.numRows);
}
>2.构造方法2
这种是快捷方式配置页面
public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) {
super(info.getComponent(), info.getUser());
label = info.isPersistable() ? iconCache.getTitleNoCache(info) :
Utilities.trim(info.getLabel(pm));
widgetInfo = null;
activityInfo = info;//这个数据不为空
spanX = spanY = 1;
}
3.CreateShortcutPreferenceController
3.1.清单文件
这种最终看meta-data里配置的fragment即可,fragment没啥东西,数据都是controller动态添加的
<activity android:name=".Settings$CreateShortcutActivity"
android:exported="true"
android:label="@string/settings_shortcut">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.shortcut.CreateShortcut" />
</activity>
3.2.常量
public class CreateShortcutPreferenceController extends BasePreferenceController {
static final String SHORTCUT_ID_PREFIX = "component-shortcut-";
//数据查询用到的intent
static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN)
.addCategory("com.android.settings.SHORTCUT")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3.3.updateState
public void updateState(Preference preference) {
if (!(preference instanceof PreferenceGroup)) {
return;
}
final PreferenceGroup group = (PreferenceGroup) preference;
group.removeAll();
final List<ResolveInfo> shortcuts = queryShortcuts();//数据参考补充1
final Context uiContext = preference.getContext();
if (shortcuts.isEmpty()) {
return;
}
PreferenceCategory category = new PreferenceCategory(uiContext);
group.addPreference(category);
int bucket = 0;
for (ResolveInfo info : shortcuts) {
//按照优先级分组,除以10的值一样就是一组
final int currentBucket = info.priority / 10;
//新的分组
boolean needDivider = currentBucket != bucket;
bucket = currentBucket;
if (needDivider) {
// add a new Category
category = new PreferenceCategory(uiContext);
group.addPreference(category);
}
//实例化选项,设置标题,key,以及点击事件
final Preference pref = new Preference(uiContext);
pref.setTitle(info.loadLabel(mPackageManager));
pref.setKey(info.activityInfo.getComponentName().flattenToString());
pref.setOnPreferenceClickListener(clickTarget -> {
if (mHost == null) {
return false;
}
final Intent shortcutIntent = createResultIntent(//参考3.3
buildShortcutIntent(uiContext, info),//补充2
info, clickTarget.getTitle());
//最终交给launcher3处理了,参考4.1
mHost.setResult(Activity.RESULT_OK, shortcutIntent);
mHost.finish();
return true;
});
//添加选项
category.addPreference(pref);
}
}
>1.queryShortcuts
List<ResolveInfo> queryShortcuts() {
final List<ResolveInfo> shortcuts = new ArrayList<>();
//查找对应的intent
final List<ResolveInfo> activities = mPackageManager.queryIntentActivities(SHORTCUT_PROBE,
PackageManager.GET_META_DATA);
if (activities == null) {
return null;
}
for (ResolveInfo info : activities) {
//先过滤两种特殊的类
if (info.activityInfo.name.contains(
Settings.OneHandedSettingsActivity.class.getSimpleName())) {
if (!OneHandedSettingsUtils.isSupportOneHandedMode()) {
continue;
}
}
if (info.activityInfo.name.endsWith(TetherSettingsActivity.class.getSimpleName())) {
if (!mConnectivityManager.isTetheringSupported()) {
continue;
}
}
if (!info.activityInfo.applicationInfo.isSystemApp()) {
//非系统应用
continue;
}
shortcuts.add(info);
}
Collections.sort(shortcuts, SHORTCUT_COMPARATOR);
return shortcuts;
}
//按优先级排序
private static final Comparator<ResolveInfo> SHORTCUT_COMPARATOR =
(i1, i2) -> i1.priority - i2.priority;
>2.buildShortcutIntent
构建要查找的intent
private static Intent buildShortcutIntent(Context context, ResolveInfo info) {
Intent intent = new Intent(SHORTCUT_PROBE)//参考3.2
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
.setClassName(info.activityInfo.packageName, info.activityInfo.name);
if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
return intent;
}
3.3.createResultIntent
Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo,
CharSequence label) {
//补充1,生成快捷方式对象
ShortcutInfo info = createShortcutInfo(mContext, shortcutIntent, resolveInfo, label);
//根据快捷方式信息获取对应的意图,参考3.4
Intent intent = mShortcutManager.createShortcutResultIntent(info);
if (intent == null) {
intent = new Intent();
}
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings))
.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo.icon != 0) {
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(
mContext,
activityInfo.applicationInfo,
activityInfo.icon,
R.layout.shortcut_badge,
mContext.getResources().getDimensionPixelSize(R.dimen.shortcut_size)));
}
return intent;
}
>1.createShortcutInfo
` private static ShortcutInfo createShortcutInfo(Context context, Intent shortcutIntent,
ResolveInfo resolveInfo, CharSequence label) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
final Icon maskableIcon;
if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) {
maskableIcon = Icon.createWithAdaptiveBitmap(createIcon(
context,
activityInfo.applicationInfo, activityInfo.icon,
R.layout.shortcut_badge_maskable,
context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)));
} else {
maskableIcon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
}
//id的生成,固定的前缀加上组件名
final String shortcutId = SHORTCUT_ID_PREFIX +
shortcutIntent.getComponent().flattenToShortString();
return new ShortcutInfo.Builder(context, shortcutId)
.setShortLabel(label)
.setIntent(shortcutIntent)
.setIcon(maskableIcon)
.build();
}
3.4.获取intent
>1.createShortcutResultIntent
public void createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId,
AndroidFuture<Intent> ret) throws RemoteException {
//..
verifyShortcutInfoPackage(packageName, shortcut);
final Intent intent;
synchronized (mLock) {
throwIfUserLockedL(userId);
// Send request to the launcher, if supported.补充2
intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
}
verifyStates();
ret.complete(intent);
}
>2.createShortcutResultIntent
public Intent createShortcutResultIntent(@NonNull ShortcutInfo inShortcut, int userId) {
// Find the default launcher activity
final int launcherUserId = mService.getParentOrSelfUserId(userId);
final String defaultLauncher = mService.getDefaultLauncher(launcherUserId);
if (defaultLauncher == null) {
Log.e(TAG, "Default launcher not found.");
return null;
}
// Make sure the launcher user is unlocked. (it's always the parent profile, so should
// really be unlocked here though.)
mService.throwIfUserLockedL(launcherUserId);
// Next, validate the incoming shortcut, etc.补充3
final PinItemRequest request = requestPinShortcutLocked(inShortcut, null, defaultLauncher,
launcherUserId);
return new Intent().putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request);
}
>3.requestPinShortcutLocked
private PinItemRequest requestPinShortcutLocked(ShortcutInfo inShortcut,
IntentSender resultIntentOriginal, String launcherPackage, int launcherUserId) {
final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked(
inShortcut.getPackage(), inShortcut.getUserId());
final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId());
final boolean existsAlready = existing != null;
final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher();
// This is the shortcut that'll be sent to the launcher.
final ShortcutInfo shortcutForLauncher;
IntentSender resultIntentToSend = resultIntentOriginal;
//快捷方式已经存在
if (existsAlready) {
validateExistingShortcut(existing);
final boolean isAlreadyPinned = mService.getLauncherShortcutsLocked(
launcherPackage, existing.getUserId(), launcherUserId).hasPinned(existing);
if (isAlreadyPinned) {
// When the shortcut is already pinned by this launcher, the request will always
// succeed, so just send the result at this point.
sendResultIntent(resultIntentOriginal, null);
// So, do not send the intent again.
resultIntentToSend = null;
}
// Pass a clone, not the original.
// Note this will remove the intent and icons.
shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
if (!isAlreadyPinned) {
// FLAG_PINNED may still be set, if it's pinned by other launchers.
shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED);
}
} else {
// If the shortcut has no default activity, try to set the main activity.
// But in the request-pin case, it's optional, so it's okay even if the caller
// has no default activity.
if (inShortcut.getActivity() == null) {
inShortcut.setActivity(mService.injectGetDefaultMainActivity(
inShortcut.getPackage(), inShortcut.getUserId()));
}
// It doesn't exist, so it must have all mandatory fields.
mService.validateShortcutForPinRequest(inShortcut);
// Initialize the ShortcutInfo for pending approval.
inShortcut.resolveResourceStrings(mService.injectGetResourcesForApplicationAsUser(
inShortcut.getPackage(), inShortcut.getUserId()));
// We should strip out the intent, but should preserve the icon.
shortcutForLauncher = inShortcut.clone(
ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER_APPROVAL);
}
// Create a request object.
final PinShortcutRequestInner inner =
new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher,
resultIntentToSend, launcherPackage, launcherUserId,
mService.injectGetPackageUid(launcherPackage, launcherUserId),
existsAlready);
return new PinItemRequest(inner, PinItemRequest.REQUEST_TYPE_SHORTCUT);
}
4.Launcher.java
4.1.handleActivityResult
- onActivityResult回调里会调用
private void handleActivityResult(
final int requestCode, final int resultCode, final Intent data) {
if (isWorkspaceLoading()) {
//workspace还在加载数据中,等完成再处理
mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data);
return;
}
mPendingActivityResult = null;
// Reset the startActivity waiting flag
final PendingRequestArgs requestArgs = mPendingRequestArgs;
setWaitingForResult(null);
if (requestArgs == null) {
return;
}
final int pendingAddWidgetId = requestArgs.getWidgetId();
Runnable exitSpringLoaded = new Runnable() {
@Override
public void run() {
mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
};
if (requestCode == REQUEST_BIND_APPWIDGET) {
// This is called only if the user did not previously have permissions to bind widgets
final int appWidgetId = data != null ?
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(
appWidgetId, requestArgs, null,
requestArgs.getWidgetHandler(),
ON_ACTIVITY_RESULT_ANIMATION_DELAY);
}
return;
}
boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
requestCode == REQUEST_CREATE_APPWIDGET);
// We have special handling for widgets
if (isWidgetDrop) {
final int appWidgetId;
int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
: -1;
if (widgetId < 0) {
appWidgetId = pendingAddWidgetId;
} else {
appWidgetId = widgetId;
}
final int result;
if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
"returned from the widget configuration activity.");
result = RESULT_CANCELED;
completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false,
() -> getStateManager().goToState(NORMAL));
} else {
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(requestArgs);
if (requestArgs.container == CONTAINER_DESKTOP) {
// When the screen id represents an actual screen (as opposed to a rank)
// we make sure that the drop page actually exists.
int newScreenId = ensurePendingDropLayoutExists(presenterPos.screenId);
requestArgs.screenId = getCellPosMapper().mapPresenterToModel(
presenterPos.cellX, presenterPos.cellY, newScreenId, CONTAINER_DESKTOP)
.screenId;
}
final CellLayout dropLayout =
mWorkspace.getScreenWithId(presenterPos.screenId);
dropLayout.setDropPending(true);
final Runnable onComplete = new Runnable() {
@Override
public void run() {
completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs);
dropLayout.setDropPending(false);
}
};
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete);
}
return;
}
if (requestCode == REQUEST_RECONFIGURE_APPWIDGET
|| requestCode == REQUEST_BIND_PENDING_APPWIDGET) {
if (resultCode == RESULT_OK) {
// Update the widget view.
completeAdd(requestCode, data, pendingAddWidgetId, requestArgs);
}
// Leave the widget in the pending state if the user canceled the configure.
return;
}
//打印了下,小节3的页面点击一个选项后,会走这里
if (requestCode == REQUEST_CREATE_SHORTCUT) {
// Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
completeAdd(requestCode, data, -1, requestArgs);//补充1,处理数据
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
} else if (resultCode == RESULT_CANCELED) {
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
}
}
mDragLayer.clearAnimatedView();
}
>1.completeAdd
private int completeAdd(
int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) {
CellPos cellPos = getCellPosMapper().mapModelToPresenter(info);
int screenId = cellPos.screenId;
if (info.container == CONTAINER_DESKTOP) {
// When the screen id represents an actual screen (as opposed to a rank) we make sure
// that the drop page actually exists.
screenId = ensurePendingDropLayoutExists(cellPos.screenId);
}
switch (requestCode) {
case REQUEST_CREATE_SHORTCUT:
//参考4.2
completeAddShortcut(intent, info.container, screenId,
cellPos.cellX, cellPos.cellY, info);
announceForAccessibility(R.string.item_added_to_workspace);
break;
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(appWidgetId, info, null, null);
break;
case REQUEST_RECONFIGURE_APPWIDGET:
getStatsLogManager().logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
break;
case REQUEST_BIND_PENDING_APPWIDGET: {
int widgetId = appWidgetId;
LauncherAppWidgetInfo widgetInfo =
completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
if (widgetInfo != null) {
// Since the view was just bound, also launch the configure activity if needed
LauncherAppWidgetProviderInfo provider = mAppWidgetManager
.getLauncherAppWidgetInfo(widgetId);
if (provider != null) {
new WidgetAddFlowHandler(provider)
.startConfigActivity(this, widgetInfo,
REQUEST_RECONFIGURE_APPWIDGET);
}
}
break;
}
}
return screenId;
}
4.2.completeAddShortcut
protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
int cellY, PendingRequestArgs args) {
if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT
|| args.getPendingIntent().getComponent() == null) {
return;
}
int[] cellXY = mTmpAddItemCellCoordinates;
CellLayout layout = getCellLayout(container, screenId);
//参考补充2
WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
this, PinRequestHelper.getPinItemRequest(data), 0);//补充1
//后边就是添加数据
}
>1.getPinItemRequest
public static PinItemRequest getPinItemRequest(Intent intent) {
Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);
return extra instanceof PinItemRequest ? (PinItemRequest) extra : null;
}
>2.createWorkspaceItemFromPinItemRequest
获取返回的数据
public static WorkspaceItemInfo createWorkspaceItemFromPinItemRequest(
Context context, final PinItemRequest request, final long acceptDelay) {
//类型是3.4.3构造方法里设置的
if (request != null && request.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT
&& request.isValid()) {
//...解析返回的数据,来源参考3.3.1
ShortcutInfo si = request.getShortcutInfo();
WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
// Apply the unbadged icon synchronously using the caching logic directly and
// fetch the actual icon asynchronously.
info.bitmap = new ShortcutCachingLogic().loadIcon(context, si);
//绑定数据
LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si);
return info;
} else {
return null;
}
}
4.3.addPendingItem
这个就是拖动结束会走,参考5.7.2
public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
int[] cell, int spanX, int spanY) {
//..
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
addAppWidgetFromDrop((PendingAddWidgetInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
processShortcutFromDrop((PendingAddShortcutInfo) info);//补充1
break;
default:
throw new IllegalStateException("Unknown item type: " + info.itemType);
}
}
>1.processShortcutFromDrop
private void processShortcutFromDrop(PendingAddShortcutInfo info) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
//设置等待结果变量
setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
//参考补充2,看是否可以正常跳转到快捷方式配置页面
if (!info.getActivityInfo(this).startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
}
}
>2.startConfigActivity
反正最终就是startXXXForResult跳转页面
public static class ShortcutConfigActivityInfoVO extends ShortcutConfigActivityInfo {
//...
public boolean startConfigActivity(Activity activity, int requestCode) {
if (getUser().equals(Process.myUserHandle())) {
return super.startConfigActivity(activity, requestCode);//参考父类
}
IntentSender is = activity.getSystemService(LauncherApps.class)
.getShortcutConfigActivityIntent(mInfo);
try {
activity.startIntentSenderForResult(is, requestCode, null, 0, 0, 0);
return true;
} catch (IntentSender.SendIntentException e) {
Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
return false;
}
}
父类,回调参考4.1
public boolean startConfigActivity(Activity activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
.setComponent(getComponent());
try {
activity.startActivityForResult(intent, requestCode);
return true;
}
return false;
}
4.4.小部件添加的方式
对应的组件是WidgetsBottomSheet
>1.方式1
- 长按应用图标,点击widgets,底部弹出一个小部件选择的页面,选择一个长按拖动到桌面
>2.方式2
- 长按桌面空白处,弹框选择widgets,找到某个应用,比如settings,选择一个长按拖动到桌面
5.WidgetsBottomSheet.java
5.1.populateAndShow
小部件底部弹框显示
public void populateAndShow(ItemInfo itemInfo) {
mOriginalItemInfo = itemInfo;
((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
onWidgetsBound();//补充1
attachToContainer();
mIsOpen = false;
animateOpen();
}
>1.onWidgetsBound
public void onWidgetsBound() {
//获取widget数据
List<WidgetItem> widgets = mActivityContext.getPopupDataProvider().getWidgetsForPackageUser(
new PackageUserKey(
mOriginalItemInfo.getTargetComponent().getPackageName(),
mOriginalItemInfo.user));
TableLayout widgetsTable = findViewById(R.id.widgets_table);
widgetsTable.removeAllViews();
//这个就是进行了分组处理
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgets, mActivityContext,
mActivityContext.getDeviceProfile(), mMaxHorizontalSpan,
mWidgetCellHorizontalPadding)
.forEach(row -> {
TableRow tableRow = new TableRow(getContext());
tableRow.setGravity(Gravity.TOP);
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
//应用数据,参考5.2.1
widget.applyFromCellItem(widgetItem);
});
widgetsTable.addView(tableRow);
});
}
5.2.WidgetCell.java
>1.applyFromCellItem
public void applyFromCellItem(WidgetItem item) {
applyFromCellItem(item, 1f);
}
public void applyFromCellItem(WidgetItem item, float previewScale) {
applyFromCellItem(item, previewScale, this::applyPreview, null);
}
public void applyFromCellItem(WidgetItem item, float previewScale,
@NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
// setPreviewSize
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, item);
mTargetPreviewWidth = widgetSize.getWidth();
mTargetPreviewHeight = widgetSize.getHeight();
mPreviewContainerScale = previewScale;
applyPreviewOnAppWidgetHostView(item);
Context context = getContext();
mItem = item;
mWidgetName.setText(mItem.label);
mWidgetName.setContentDescription(
context.getString(R.string.widget_preview_context_description, mItem.label));
mWidgetDims.setText(context.getString(R.string.widget_dims_format,
mItem.spanX, mItem.spanY));
mWidgetDims.setContentDescription(context.getString(
R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY));
if (ATLEAST_S && mItem.widgetInfo != null) {
CharSequence description = mItem.widgetInfo.loadDescription(context);
if (description != null && description.length() > 0) {
mWidgetDescription.setText(description);
mWidgetDescription.setVisibility(VISIBLE);
} else {
mWidgetDescription.setVisibility(GONE);
}
}
//有activity的是shortcut,没有的是widget
//参考2.1数据来源,以及2.4的构造方法
if (item.activityInfo != null) {
setTag(new PendingAddShortcutInfo(item.activityInfo));
} else {
setTag(new PendingAddWidgetInfo(item.widgetInfo, mSourceContainer));
}
ensurePreviewWithCallback(callback, cachedPreview);
}
5.3.父类BaseWidgetSheet
>1.onClick
点击的话就是弹个提示
public final void onClick(View v) {
Object tag = null;
if (v instanceof WidgetCell) {
tag = v.getTag();
} else if (v.getParent() instanceof WidgetCell) {
tag = ((WidgetCell) v.getParent()).getTag();
}
if (tag instanceof PendingAddShortcutInfo) {
mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
} else {
mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
}
}
>2.onLongClick
长按开始拖拽
public boolean onLongClick(View v) {
v.cancelLongPress();
if (!ItemLongClickListener.canStartDrag(mActivityContext)) return false;
if (v instanceof WidgetCell) {//参考补充3
return beginDraggingWidget((WidgetCell) v);
} else if (v.getParent() instanceof WidgetCell) {
return beginDraggingWidget((WidgetCell) v.getParent());
}
return true;
}
>3.beginDraggingWidget
private boolean beginDraggingWidget(WidgetCell v) {
WidgetImageView image = v.getWidgetView();
//图片和预览都为空,中断
if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) {
return false;
}
//交给helper类处理了,参考5.4
PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview(), v.getAppWidgetHostViewScale());
dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
if (image.getDrawable() != null) {
int[] loc = new int[2];
getPopupContainer().getLocationInDragLayer(image, loc);
//开始拖动,参考5.4.2
dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
} else {
NavigableAppWidgetHostView preview = v.getAppWidgetHostViewPreview();
int[] loc = new int[2];
getPopupContainer().getLocationInDragLayer(preview, loc);
Rect r = new Rect();
preview.getWorkspaceVisualDragBounds(r);
dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(),
new Point(loc[0], loc[1]), this, new DragOptions());
}
close(true);
return true;
}
5.4.PendingItemDragHelper
>1.构造方法
public PendingItemDragHelper(View view) {
super(view);
//通过tag获取数据,参考5.2.1设置数据
mAddInfo = (PendingAddItemInfo) view.getTag();
mEnforcedRoundedCornersForWidget = RoundedCornerEnforcement.computeEnforcedRadius(
view.getContext());
}
>2.startDrag
- launcher用的controller是LauncherDragController.java
public void startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth,
Point screenPos, DragSource source, DragOptions options) {
//....
// Start the drag
if (mAppWidgetHostViewPreview != null) {
launcher.getDragController().startDrag(mAppWidgetHostViewPreview, draggableView,
dragLayerX, dragLayerY, source, mAddInfo, dragOffset, dragRegion, scale, scale,
options);
} else {
launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
source, mAddInfo, dragOffset, dragRegion, scale, scale, options);
}
}
5.5.DragController.java
>1.startDrag
LauncherDragController.java
protected DragView startDrag(
//....
mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
>2.onDragEvent
jiaogei
public boolean onDragEvent(DragEvent event) {
return mDragDriver != null && mDragDriver.onDragEvent(event);
}
>3.onDriverDragEnd
public void onDriverDragEnd(float x, float y) {
if (!endWithFlingAnimation()) {
//参考补充4
drop(findDropTarget((int) x, (int) y, mCoordinatesTemp), null);
}
endDrag();
}
>4.drop
protected void drop(DropTarget dropTarget, Runnable flingAnimation) {
final int[] coordinates = mCoordinatesTemp;
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
// Move dragging to the final target.
if (dropTarget != mLastDropTarget) {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragObject);
}
mLastDropTarget = dropTarget;
if (dropTarget != null) {
dropTarget.onDragEnter(mDragObject);
}
}
mDragObject.dragComplete = true;
if (mIsInPreDrag) {
if (dropTarget != null) {
dropTarget.onDragExit(mDragObject);
}
return;
}
// Drop onto the target.
boolean accepted = false;
if (dropTarget != null) {
dropTarget.onDragExit(mDragObject);
if (dropTarget.acceptDrop(mDragObject)) {
if (flingAnimation != null) {
flingAnimation.run();
} else {//参考5.7
dropTarget.onDrop(mDragObject, mOptions);
}
accepted = true;
}
}
final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
dispatchDropComplete(dropTargetAsView, accepted);
}
5.6.DragDriver
###> 1.create
public static DragDriver create(DragController dragController, DragOptions options,
Consumer<MotionEvent> sec) {
if (options.simulatedDndStartPoint != null) {
if (options.isAccessibleDrag) {
return null;
}
return new SystemDragDriver(dragController, sec);
} else {
return new InternalDragDriver(dragController, sec);
}
}
>2.InternalDragDriver
mEventListener就是DragController
static class InternalDragDriver extends DragDriver {
private final DragController mDragController;
InternalDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
super(dragController, sec);
mDragController = dragController;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mSecondaryEventConsumer.accept(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
mEventListener.onDriverDragMove(mDragController.getX(ev),
mDragController.getY(ev));
break;
case MotionEvent.ACTION_UP:
mEventListener.onDriverDragMove(mDragController.getX(ev),
mDragController.getY(ev));
//参考5.5.3
mEventListener.onDriverDragEnd(mDragController.getX(ev),
mDragController.getY(ev));
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
break;
}
return true;
}
5.7.Workspace.java
public class Workspace<T extends View & PageIndicator> extends PagedView<T>
implements DropTarget, DragSource, View.OnTouchListener,
DragController.DragListener, Insettable, StateHandler<LauncherState>,
WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
>1.onDrop
public void onDrop(final DragObject d, DragOptions options) {
//....
if (d.dragSource != this || mDragInfo == null) {
final int[] touchXY = new int[]{(int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1]};
//补充2
onDropExternal(touchXY, dropTargetLayout, d);
} else {
>2.onDropExternal
private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
//...
if (info instanceof PendingAddItemInfo) {
final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
//..
Runnable onAnimationCompleteRunnable = new Runnable() {
@Override
public void run() {
// Normally removeExtraEmptyScreen is called in Workspace#onDrop, but when
// adding an item that may not be dropped right away (due to a config activity)
// we defer the removal until the activity returns.
deferRemoveExtraEmptyScreen();
//参考4.3
mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
item.spanX, item.spanY);
}
};
//..
animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
animationStyle, finalView, true);