1.简介
历史原因,我们用的是android13最早的一版代码,里边各种坑。有时候发现最新的代码是好的,我们的就有问题,想对比下代码,差别太大了,无从下手,只能理清老代码逻辑找原因
2..分屏异常
2.1.描述:
桌面长按一个快捷方式,选择split left或者right都行,完事再随便选个历史应用,会发现这个快捷方式对应的分屏黑一下就没了,分屏失败
2.2.日志
- 我的快捷方式声明的action指向的是一个MyJavaActivity的页面,没看到对应的信息,只看到demo的启动页信息。
- 第一行日志提示Intent不匹配,然后看下打印的intent,这个好像是快捷方式默认的intent
>1.分屏日志
PackageManager W Intent does not match component's intent filter: Intent { act=android.intent.action.MAIN cat=[com.android.launcher3.DEEP_SHORTCUT] flg=0x10200000 pkg=com.example.myapp2023 cmp=com.example.myapp2023/.test.MyActivity (has extras) }
PackageManager W Access blocked: ComponentInfo{com.example.myapp2023/com.example.myapp2023.test.MyActivity}
ActivitivityManager I START u0 {act=android.intent.action.MAIN cat=[com.android.launcher3.DEEP_SHORTCUT] flg=0x10200000 pkg=com.example.myapp2023 cmp=com.example.myapp2023/.test.MyActivity (has extras)} from uid 10152
>2.正常日志
这个是直接点击快捷方式的日志
START u0 {act=com.test.myjava flg=0x1000c000 cmp=com.example.myapp2023/.java.MyJavaActivity bnds=[672,169][792,275]} from uid 10189
2.3.思路
>1.直接点击快捷方式
- 见小节3启动流程,最终启动逻辑见3.7分析,可以看到是传递了包名以及shortcutId处理的
>2.分屏点击的快捷方式
- 流程见小节4,小节4.10.4里是启动分屏的逻辑
- 可以看到,区分是否有shortcut,而旧代码是没有这块逻辑的,直接传的Intent,而快捷方式的intent是需要特殊处理的,不处理的话这个intent查找不到对应的activity也就无法启动。
- 最后再看日志相关的代码
3.ItemClickHandler
桌面图标的点击事件处理类
3.1.onClick
private static void onClick(View v) {
if (v.getWindowToken() == null) return;
Launcher launcher = Launcher.getLauncher(v.getContext());
//桌面状态切换中,不做处理,补充1
if (!launcher.getWorkspace().isFinishedSwitchingState()) return;
Object tag = v.getTag();
//根据数据类型做不同的处理
//桌面的应用图标,或者快捷方式走这里
if (tag instanceof WorkspaceItemInfo) {
onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);//见3.2
} else if (tag instanceof FolderInfo) {
if (v instanceof FolderIcon) {
onClickFolderIcon(v);//补充2,文件夹展开
}
} else if (tag instanceof AppInfo) {
//allapps页面的走这里
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);//见3.3
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {//见3.4
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
}
} else if (tag instanceof ItemClickProxy) {//这个是自定义的点击事件
((ItemClickProxy) tag).onItemClicked(v);
}
}
>1.isFinishedSwitchingState
是否完成了状态切换,也就是非状态切换中或者切换进度大于50%
public boolean isFinishedSwitchingState() {
return !mIsSwitchingState
|| (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
}
>2.onClickFolderIcon
private static void onClickFolderIcon(View v) {
Folder folder = ((FolderIcon) v).getFolder();
if (!folder.isOpen() && !folder.isDestroyed()) {
// 打开文件夹
folder.animateOpen();
}
}
3.2.onClickAppShortcut
public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) {
if (shortcut.isDisabled() && handleDisabledItemClicked(shortcut, launcher)) {
//参考3.5
return;
}
// Check for abandoned promise
if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
String packageName = shortcut.getIntent().getComponent() != null
? shortcut.getIntent().getComponent().getPackageName()
: shortcut.getIntent().getPackage();
if (!TextUtils.isEmpty(packageName)) {
//补充2,跳转到应用市场
onClickPendingAppItem(
v,
launcher,
packageName,
(shortcut.runtimeStatusFlags
& ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0);
return;
}
}
// Start activities,见3.6
startAppShortcutOrInfoActivity(v, shortcut, launcher);
}
>1.hasPromiseIconUi
app暂时不可用,并且不支持web ui
public boolean hasPromiseIconUi() {
return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
}
//可以简单理解为app暂时还不可用,比如恢复中,自动安装中。
public final boolean isPromise() {
return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON);
}
>2.onClickPendingAppItem
private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
boolean downloadStarted) {
if (downloadStarted) {
//已经开始下载了,跳转到市场应用
startMarketIntentForPackage(v, launcher, packageName);
return;
}
UserHandle user = v.getTag() instanceof ItemInfo
? ((ItemInfo) v.getTag()).user : Process.myUserHandle();
//过期提示对话框
new AlertDialog.Builder(launcher)
.setTitle(R.string.abandoned_promises_title)
.setMessage(R.string.abandoned_promise_explanation)
.setPositiveButton(R.string.abandoned_search,
(d, i) -> startMarketIntentForPackage(v, launcher, packageName))
.setNeutralButton(R.string.abandoned_clean_this,
(d, i) -> launcher.getWorkspace()
.persistRemoveItemsByMatcher(ItemInfoMatcher.ofPackages(
Collections.singleton(packageName), user),
"user explicitly removes the promise app icon"))
.create().show();
}
>3.startMarketIntentForPackage
根据应用包名跳转到应用市场页面
private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
ItemInfo item = (ItemInfo) v.getTag();
if (Utilities.ATLEAST_Q) {
SessionInfo sessionInfo = InstallSessionHelper.INSTANCE.get(launcher)
.getActiveSessionInfo(item.user, packageName);
if (sessionInfo != null) {
LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
try {
launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
launcher.getActivityLaunchOptions(v, item).toBundle());
return;
}
}
}
// Fallback to using custom market intent.
Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
launcher.startActivitySafely(v, intent, item);
}
>4.startAppShortcutOrInfoActivity
private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
Intent intent;
if (item instanceof ItemInfoWithIcon
&& (((ItemInfoWithIcon) item).runtimeStatusFlags
& ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
//安装进行中。
ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
//应用市场的intent
intent = new PackageManagerHelper(launcher)
.getMarketIntent(appInfo.getTargetComponent().getPackageName());
} else {
intent = item.getIntent();
}
if (intent == null) {
throw new IllegalArgumentException("Input must have a valid intent");
}
if (item instanceof WorkspaceItemInfo) {
WorkspaceItemInfo si = (WorkspaceItemInfo) item;
//支持web ui,并且是ACTION_VIEW
if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)
&& Intent.ACTION_VIEW.equals(intent.getAction())) {
//copy一份,设置package为空
intent = new Intent(intent);
intent.setPackage(null);
}
if ((si.options & WorkspaceItemInfo.FLAG_START_FOR_RESULT) != 0) {
//需要返回结果的
launcher.startActivityForResult(item.getIntent(), 0);
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
return;
}
}
if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}
launcher.startActivitySafely(v, intent, item);
}
3.3.startAppShortcutOrInfoActivity
private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "start: startAppShortcutOrInfoActivity");
Intent intent;
if (item instanceof ItemInfoWithIcon
&& (((ItemInfoWithIcon) item).runtimeStatusFlags
& ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
intent = new PackageManagerHelper(launcher)
.getMarketIntent(appInfo.getTargetComponent().getPackageName());
} else {
intent = item.getIntent();
}
if (intent == null) {
throw new IllegalArgumentException("Input must have a valid intent");
}
if (item instanceof WorkspaceItemInfo) {
WorkspaceItemInfo si = (WorkspaceItemInfo) item;
if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)
&& Intent.ACTION_VIEW.equals(intent.getAction())) {
// make a copy of the intent that has the package set to null
// we do this because the platform sometimes disables instant
// apps temporarily (triggered by the user) and fallbacks to the
// web ui. This only works though if the package isn't set
intent = new Intent(intent);
intent.setPackage(null);
}
if ((si.options & WorkspaceItemInfo.FLAG_START_FOR_RESULT) != 0) {
launcher.startActivityForResult(item.getIntent(), 0);
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
launcher.logAppLaunch(launcher.getStatsLogManager(), item, instanceId);
return;
}
}
if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}
launcher.startActivitySafely(v, intent, item);
}
3.4.onClickPendingWidget
private static void onClickPendingWidget(PendingAppWidgetHostView v, Launcher launcher) {
if (launcher.getPackageManager().isSafeMode()) {
Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
return;
}
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
if (v.isReadyForClickSetup()) {
LauncherAppWidgetProviderInfo appWidgetInfo = new WidgetManagerHelper(launcher)
.findProvider(info.providerName, info.user);
if (appWidgetInfo == null) {
return;
}
WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
// This should not happen, as we make sure that an Id is allocated during bind.
return;
}
addFlowHandler.startBindFlow(launcher, info.appWidgetId, info,
REQUEST_BIND_PENDING_APPWIDGET);
} else {
addFlowHandler.startConfigActivity(launcher, info, REQUEST_RECONFIGURE_APPWIDGET);
}
} else {
final String packageName = info.providerName.getPackageName();
onClickPendingAppItem(v, launcher, packageName, info.installProgress >= 0);
}
}
3.5.handleDisabledItemClicked
判断item是否可以点击
public static boolean handleDisabledItemClicked(WorkspaceItemInfo shortcut, Context context) {
//解析flag
final int disabledFlags = shortcut.runtimeStatusFlags
& WorkspaceItemInfo.FLAG_DISABLED_MASK;
//见补充1,处理app版本太低的情况,为true表示已弹框提示,为false表示未处理
if (maybeCreateAlertDialogForShortcut(shortcut, context)) {
return true;
}
if ((disabledFlags
& ~FLAG_DISABLED_SUSPENDED
& ~FLAG_DISABLED_QUIET_USER) == 0) {
// 如果应用程序只是因为上述标志而被禁用,那么继续启动活动
// Framework 将告诉用户应用程序暂停的原因。
return false;
} else {
if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
// 快捷方式有声明不可用的提示文字,那就直接用
Toast.makeText(context, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
return true;
}
//那根据flag显示不同的提示文字,默认文字,“应用不可用”
int error = R.string.activity_not_available;
if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
//安全模式下无法使用已下载的应用
error = R.string.safemode_shortcut_error;
} else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0
|| (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
//被禁用或者用户还没unlock,提示 快捷方式不可用
error = R.string.shortcut_not_available;
}
Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
return true;
}
}
>1.maybeCreateAlertDialogForShortcut
返回true表示已处理
private static boolean maybeCreateAlertDialogForShortcut(final WorkspaceItemInfo shortcut,
Context context) {
try {
final Launcher launcher = Launcher.getLauncher(context);
//快捷方式,它的应用程序版本太低
if (shortcut.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& shortcut.isDisabledVersionLower()) {
//应用市场intent
final Intent marketIntent = shortcut.getMarketIntent(context);
if (marketIntent == null) {
return false;
}
//弹框提示用户更新或者删除图标
new AlertDialog.Builder(context)
.setTitle(R.string.dialog_update_title)
.setMessage(R.string.dialog_update_message)
.setPositiveButton(R.string.dialog_update, (d, i) -> {
// 跳转到应用市场更新应用
context.startActivity(marketIntent);
})
.setNeutralButton(R.string.dialog_remove, (d, i) -> {
// 移除图标
launcher.getWorkspace().persistRemoveItemsByMatcher(ItemInfoMatcher
.ofShortcutKeys(Collections.singleton(ShortcutKey
.fromItemInfo(shortcut))),
"user explicitly removes disabled shortcut");
})
.create()
.show();
return true;
}
}
return false;
}
3.6.startActivitySafely
>1.QuickstepLauncher.java
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
// 只有taskbar controller为空的时候才暂停
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
//补充2
boolean started = super.startActivitySafely(v, intent, item);
if (getTaskbarUIController() == null && !started) {
mHotseatPredictionController.setPauseUIUpdate(false);
}
return started;
}
>2.父类Launcher.java
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
if (!hasBeenResumed()) {
//launcher状态是没resume的时候,在resume以后再次调用这个方法
addOnResumeCallback(() -> startActivitySafely(v, intent, item));
if (mOnDeferredActivityLaunchCallback != null) {
mOnDeferredActivityLaunchCallback.run();
mOnDeferredActivityLaunchCallback = null;
}
return true;
}
//补充3
boolean success = super.startActivitySafely(v, intent, item);
if (success && v instanceof BubbleTextView) {
//保持press状态
BubbleTextView btv = (BubbleTextView) v;
btv.setStayPressed(true);
//桌面resume的时候取消press
addOnResumeCallback(() -> btv.setStayPressed(false));
}
return success;
}
>3.父类ActivityContext.java
default boolean startActivitySafely(
View v, Intent intent, @Nullable ItemInfo item) {
Context context = (Context) this;
//安全模式下,非系统应用
if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) {
Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return false;
}
Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null;
UserHandle user = item == null ? null : item.user;
// Prepare intent
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (v != null) {
intent.setSourceBounds(Utilities.getViewBounds(v));
}
try {
boolean isShortcut = (item instanceof WorkspaceItemInfo)
&& (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|| item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
&& !((WorkspaceItemInfo) item).isPromise();
if (isShortcut) {
//补充4,快捷方式需要特殊处理
startShortcutIntentSafely(intent, optsBundle, item);
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
context.startActivity(intent, optsBundle);
} else {
context.getSystemService(LauncherApps.class).startMainActivity(
intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
}
return true;
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
}
return false;
}
>4.startShortcutIntentSafely
default void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
try {
try {
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
String packageName = intent.getPackage();
//补充5
startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
} else {
// Could be launching some bookkeeping activity
((Context) this).startActivity(intent, optsBundle);
}
}
}
}
>5.startShortcut
default void startShortcut(String packageName, String id, Rect sourceBounds,
Bundle startActivityOptions, UserHandle user) {
if (GO_DISABLE_WIDGETS) {//重写了,为false
return;
}
try {
((Context) this).getSystemService(LauncherApps.class).startShortcut(packageName, id,
sourceBounds, startActivityOptions, user);
}
}
3.7.总结
- 快捷方式相关的(ITEM_TYPE_DEEP_SHORTCUT),流程见3.1》3.2》3.6.4》3.6.5
- 参考3.6.5,快捷方式点击后,最终调用的LauncherApps的startShortcut方法进行操作的,具体下篇分析。
4.ItemLongClickListener
- 桌面图标的长按事件处理类,分workspace和allApps
- 两个逻辑差不多,这里就研究一种即可,只看弹框相关的
public static final OnLongClickListener INSTANCE_WORKSPACE =
ItemLongClickListener::onWorkspaceItemLongClick;
public static final OnLongClickListener INSTANCE_ALL_APPS =
ItemLongClickListener::onAllAppsItemLongClick;
4.1.onWorkspaceItemLongClick
private static boolean onWorkspaceItemLongClick(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
if (!canStartDrag(launcher)) return false;
if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
if (!(v.getTag() instanceof ItemInfo)) return false;
launcher.setWaitingForResult(null);
beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
return true;
}
public static void beginDrag(View v, Launcher launcher, ItemInfo info,
DragOptions dragOptions) {
if (info.container >= 0) {
Folder folder = Folder.getOpen(launcher);
if (folder != null) {
if (!folder.getIconsInReadingOrder().contains(v)) {
folder.close(true);
} else {
folder.startDrag(v, dragOptions);
return;
}
}
}
CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
//见4.2
launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
}
4.2.startDrag
public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
View child = cellInfo.cell;
mDragInfo = cellInfo;
child.setVisibility(INVISIBLE);
//..
beginDragShared(child, this, options);
}
public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
//..
if (child instanceof BubbleTextView) {
BubbleTextView btv = (BubbleTextView) child;
//非拖动中
if (!dragOptions.isAccessibleDrag) {
//长按操作,见补充1
dragOptions.preDragCondition = btv.startLongPressAction();
}
if (btv.isDisplaySearchResult()) {
dragOptions.preDragEndScale = (float) mAllAppsIconSize / btv.getIconSize();
}
}
final DragView dv;
if (contentView instanceof View) {
if (contentView instanceof LauncherAppWidgetHostView) {
mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
}
dv = mDragController.startDrag(
contentView,
draggableView,
dragLayerX,
dragLayerY,
source,
dragObject,
dragVisualizeOffset,
dragRect,
scale * iconScale,
scale,
dragOptions);
} else {
dv = mDragController.startDrag(
drawable,
draggableView,
dragLayerX,
dragLayerY,
source,
dragObject,
dragVisualizeOffset,
dragRect,
scale * iconScale,
scale,
dragOptions);
}
return dv;
}
>1.startLongPressAction
//BubbleTextView.java
public PreDragCondition startLongPressAction() {
//见4.3
PopupContainerWithArrow popup = PopupContainerWithArrow.showForIcon(this);
return popup != null ? popup.createPreDragCondition(true) : null;
}
4.3.PopupContainerWithArrow.java
>1.showForIcon
public static PopupContainerWithArrow<Launcher> showForIcon(BubbleTextView icon) {
Launcher launcher = Launcher.getLauncher(icon.getContext());
if (getOpen(launcher) != null) {
// There is already an items container open, so don't open this one.
icon.clearFocus();
return null;
}
ItemInfo item = (ItemInfo) icon.getTag();
if (!ShortcutUtil.supportsShortcuts(item)) {
return null;
}
//布局见补充2
final PopupContainerWithArrow<Launcher> container =
(PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
container.configureForLauncher(launcher);
PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
//见4.4.1
container.populateAndShow(icon,
popupDataProvider.getShortcutCountForItem(item),//见4.5.1
popupDataProvider.getNotificationKeysForItem(item),//见4.5.2
launcher.getSupportedShortcuts()//见4.6.1,返回的是Factory集合
//把Factory转化为shortcut,具体参考对应的factory,为空不支持
.map(s -> s.getShortcut(launcher, item, icon))
.filter(Objects::nonNull)//过滤掉为空的,也就是不支持对应的快捷方式
.collect(Collectors.toList()));
//根据包名查找支持的小部件
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
container.requestFocus();
return container;
}
>2.popup_container.xml
里边有2个child,一个是快捷方式容器,一个是通知容器
<!--自定义的悬浮容器,线性布局-->
<com.android.launcher3.popup.PopupContainerWithArrow
android:id="@+id/popup_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:clipChildren="false"
android:orientation="vertical">
<LinearLayout
android:id="@+id/deep_shortcuts_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="@string/popup_container_iterate_children"
android:elevation="@dimen/deep_shortcuts_elevation"
android:orientation="vertical"/>
<com.android.launcher3.notification.NotificationContainer
android:id="@+id/notification_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
</com.android.launcher3.popup.PopupContainerWithArrow>
4.4.PopupContainerWithArrow.java
自定义的线程布局
public class PopupContainerWithArrow<T extends Context & ActivityContext>
extends ArrowPopup<T> implements DragSource, DragController.DragListener {
public abstract class ArrowPopup<T extends Context & ActivityContext>
extends AbstractFloatingView {
public abstract class AbstractFloatingView extends LinearLayout implements TouchController {
>1.populateAndShow
- shortcutCount:这个是指app清单文件里配置的shortcut或者动态添加的那种。
public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> shortcuts) {
mNumNotifications = notificationKeys.size();
mOriginalIcon = originalIcon;
boolean hasDeepShortcuts = shortcutCount > 0;
mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
//有通知的话,给通知容器里添加数据
if (mNumNotifications > 0) {
// Add notification entries
if (mNotificationContainer == null) {
mNotificationContainer = findViewById(R.id.notification_container);
mNotificationContainer.setVisibility(VISIBLE);
mNotificationContainer.setPopupView(this);
} else {
mNotificationContainer.setVisibility(GONE);
}
updateNotificationHeader();
}
int viewsToFlip = getChildCount();
mSystemShortcutContainer = this;
if (mDeepShortcutContainer == null) {
mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
}
if (hasDeepShortcuts) {
//非小部件快捷方式,除了4.8.1那种的都算
List<SystemShortcut> systemShortcuts = shortcuts
.stream()
.filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets))
.collect(Collectors.toList());
//小部件快捷方式,见4.8.1
Optional<SystemShortcut.Widgets> widgetShortcutOpt = shortcuts
.stream()
.filter(shortcut -> shortcut instanceof SystemShortcut.Widgets)
.map(SystemShortcut.Widgets.class::cast)
.findFirst();
mContainerWidth = Math.max(mContainerWidth,
systemShortcuts.size() * getResources()
.getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)
);
mDeepShortcutContainer.setVisibility(View.VISIBLE);
//先加载app自带的shortcut
for (int i = shortcutCount; i > 0; i--) {
//布局见补充2
DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, mDeepShortcutContainer);
v.getLayoutParams().width = mContainerWidth;
mShortcuts.add(v);
}
//快捷方式是否隐藏,补充3
updateHiddenShortcuts();
//小部件快捷方式
if (widgetShortcutOpt.isPresent()) {
if (mWidgetContainer == null) {
//线性布局
mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
this);
}
//最终效果见4.8.1
initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
}
//补充4,小部件以外的其他快捷方式
initializeSystemShortcuts(systemShortcuts);
} else {//应用没有定义快捷方式的情况下,
mDeepShortcutContainer.setVisibility(View.GONE);
if (!shortcuts.isEmpty()) {
for (SystemShortcut shortcut : shortcuts) {
initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
}
}
}
//这个可能根据需求,child进行空翻,比如显示在icon下的是ABC,显示在icon上顺序就是CBA
reorderAndShow(viewsToFlip);
ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setAccessibilityPaneTitle(getTitleForAccessibility());
}
mOriginalIcon.setForceHideDot(true);
//进行动画
setLayoutTransition(new LayoutTransition());
// Load the shortcuts on a background thread and update the container as it animates.
MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
mActivityContext, originalItemInfo, new Handler(Looper.getMainLooper()),
this, mShortcuts, notificationKeys));
}
>2.deep_shortcut.xml
<com.android.launcher3.shortcuts.DeepShortcutView
android:layout_width="@dimen/bg_popup_item_width"
android:layout_height="@dimen/bg_popup_item_height"
android:elevation="@dimen/deep_shortcuts_elevation"
android:background="@drawable/middle_item_primary"
android:theme="@style/PopupItem" >
<!--drawableEnd两条横岗的图标,代码这个条目可以长按拖动到桌面-->
<com.android.launcher3.shortcuts.DeepShortcutTextView
style="@style/BaseIcon"
android:id="@+id/bubble_text"
android:background="?android:attr/selectableItemBackground"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
android:paddingEnd="@dimen/popup_padding_end"
android:drawableEnd="@drawable/ic_drag_handle"//末尾两条横岗的图片
android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
android:singleLine="true"
android:ellipsize="end"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
launcher:layoutHorizontal="true"
launcher:iconDisplay="shortcut_popup"
launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
<!--快捷方式图标-->
<View
android:id="@+id/icon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="@dimen/deep_shortcut_icon_size"
android:layout_marginStart="@dimen/popup_padding_start"
android:layout_gravity="start|center_vertical"
android:background="@drawable/ic_deepshortcut_placeholder"/>
</com.android.launcher3.shortcuts.DeepShortcutView>
>3.updateHiddenShortcuts
超过最大数的shortcut隐藏
protected void updateHiddenShortcuts() {
//显示通知的话,最大是2,否则是4
int allowedCount = mNotificationContainer != null
? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
int total = mShortcuts.size();
for (int i = 0; i < total; i++) {
DeepShortcutView view = mShortcuts.get(i);
view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
}
}
>4.initializeSystemShortcuts
private void initializeSystemShortcuts(List<SystemShortcut> shortcuts) {
if (shortcuts.isEmpty()) {
return;
}
//只有一个,显示完成的条目
if (shortcuts.size() == 1) {
initializeSystemShortcut(R.layout.system_shortcut, this, shortcuts.get(0));
return;
}
//先加载一个容器
mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
for (SystemShortcut shortcut : shortcuts) {
//完事其他shorcut只加载icon,加入上边的容器里
initializeSystemShortcut(
R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
shortcut);
}
}
4.5.PopupDataProvider.java
>1.getShortcutCountForItem
其实就是应用自定义的shortuct的个数
public int getShortcutCountForItem(ItemInfo info) {
//应用不可用或者不支持快捷方式
if (!ShortcutUtil.supportsDeepShortcuts(info)) {
return 0;
}
ComponentName component = info.getTargetComponent();
if (component == null) {
return 0;
}
Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user));
return count == null ? 0 : count;
}
>2.getNotificationKeysForItem
获取通知数据
public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
DotInfo dotInfo = getDotInfoForItem(info);
return dotInfo == null ? Collections.EMPTY_LIST
: getNotificationsForItem(info, dotInfo.getNotificationKeys());
}
4.6.QuickstepLauncher.java
>1.getSupportedShortcuts
这里是系统默认的一些,比如app info, widgets ,split left
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
RecentsView recentsView = getOverviewPanel();
//获取分屏相关的选项
List<SplitPositionOption> positions =
recentsView.getPagedOrientationHandler().getSplitPositionOptions(
mDeviceProfile);
List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
for (SplitPositionOption position : positions) {
//数据见4.7.1
splitShortcuts.add(getSplitSelectShortcutByPosition(position));
}
//加入base
base = Stream.concat(base, splitShortcuts.stream());
}
继续加入父类默认的,以及hotseat推荐控制器里的
return Stream.concat(
Stream.of(mHotseatPredictionController),
Stream.concat(base, super.getSupportedShortcuts()));
}
父类
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
4.7.QuickstepSystemShortcut
>1.getSplitSelectShortcutByPosition
static SystemShortcut.Factory<QuickstepLauncher> getSplitSelectShortcutByPosition(
SplitPositionOption position) {
//交给SplitSelectSystemShortcut处理,见补充2
return (activity, itemInfo, originalView) ->
new QuickstepSystemShortcut.SplitSelectSystemShortcut(activity, itemInfo,
originalView, position);
}
>2.SplitSelectSystemShortcut
分屏用到的shortuct,效果图
public SplitSelectSystemShortcut(QuickstepLauncher launcher, ItemInfo itemInfo,
View originalView, SplitPositionOption position) {
super(position.iconResId, position.textResId, launcher, itemInfo, originalView);
//..
public void onClick(View view) {
// Initiate splitscreen from the Home screen or Home All Apps
Bitmap bitmap;
Intent intent;
//根据数据类型获取intent
if (mItemInfo instanceof WorkspaceItemInfo) {
final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo;
bitmap = workspaceItemInfo.bitmap.icon;
intent = workspaceItemInfo.intent;
} else if (mItemInfo instanceof com.android.launcher3.model.data.AppInfo) {
final com.android.launcher3.model.data.AppInfo appInfo =
(com.android.launcher3.model.data.AppInfo) mItemInfo;
bitmap = appInfo.bitmap.icon;
intent = appInfo.intent;
} else {
return;
}
RecentsView recentsView = mTarget.getOverviewPanel();
//查看是否有app运行实例,有的话直接用,见6.2
recentsView.findLastActiveTaskAndDoSplitOperation(
intent.getComponent(),
(Consumer<Task>) foundTask -> {
SplitSelectSource source = new SplitSelectSource(mOriginalView,
new BitmapDrawable(bitmap), intent, mPosition, mItemInfo,
splitEvent, foundTask);
if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
startSplitToHome(source);
} else {
recentsView.initiateSplitSelect(source);//见4.9
}
}
);
}
4.8.SystemShortcut.java
public abstract class SystemShortcut<T extends Context & ActivityContext> extends ItemInfo
implements View.OnClickListener {
- 子类有如下三种,4.7.2也是
>1.Widgets
public static final Factory<Launcher> WIDGETS = (launcher, itemInfo, originalView) -> {
if (itemInfo.getTargetComponent() == null) return null;
//看是否有支持的小部件
final List<WidgetItem> widgets =
launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
if (widgets.isEmpty()) {
return null;
}
return new Widgets(launcher, itemInfo, originalView);
};
public static class Widgets extends SystemShortcut<Launcher> {
public Widgets(Launcher target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo,
originalView);
}
@Override
public void onClick(View view) {
AbstractFloatingView.closeAllOpenViews(mTarget);
WidgetsBottomSheet widgetsBottomSheet =
(WidgetsBottomSheet) mTarget.getLayoutInflater().inflate(
R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false);
widgetsBottomSheet.populateAndShow(mItemInfo);
}
}
>2.AppInfo
下图,如果显示不下,就是补充1里弹框右上角那个感叹号。
public static class AppInfo<T extends Context & ActivityContext> extends SystemShortcut<T> {
public AppInfo(T target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
itemInfo, originalView);
}
public void onClick(View view) {
dismissTaskMenuView(mTarget);
Rect sourceBounds = Utilities.getViewBounds(view);
new PackageManagerHelper(mTarget).startDetailsActivityForInfo(
mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
}
>3.Install
public static class Install extends SystemShortcut<BaseDraggingActivity> {
public Install(BaseDraggingActivity target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
target, itemInfo, originalView);
}
public void onClick(View view) {
Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
mItemInfo.getTargetComponent().getPackageName());
mTarget.startActivitySafely(view, intent, mItemInfo);
AbstractFloatingView.closeAllOpenViews(mTarget);
}
}
4.9.LauncherRecentsView.java
>1.initiateSplitSelect
public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
//见父类6.3,这里就是设置数据
super.initiateSplitSelect(splitSelectSource);
//进入recent页面
mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
>2.父类RecentsView.java
public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
mSplitSelectSource = splitSelectSource;
//见4.10
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTask);
}
>3.canLaunchFullscreenTask
protected boolean canLaunchFullscreenTask() {
return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
}
4.10.SplitSelectStateController.java
看完两个方法,发现它就设置了数据,啥也没干。得等第二个分屏数据设置完才会用到
>1.setInitialTaskSelect
public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition,
@NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent,
@Nullable Task alreadyRunningTask) {
if (alreadyRunningTask != null) {
mInitialTaskId = alreadyRunningTask.key.id;
} else {
mInitialTaskIntent = intent;
mUser = itemInfo.user;
}
setInitialData(stagePosition, splitEvent, itemInfo);
}
>2.setInitialData
private void setInitialData(@StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
mItemInfo = itemInfo;
mStagePosition = stagePosition;
mSplitEvent = splitEvent;
}
>3.isBothSplitAppsConfirmed
分屏需要的两个task的数据是否都已经设置
public boolean isBothSplitAppsConfirmed() {
return isInitialTaskIntentSet() && isSecondTaskIntentSet();
}
private boolean isInitialTaskIntentSet() {
return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null);
}
private boolean isSecondTaskIntentSet() {
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
}
>4.launchSplitTasks
开始加载两个分屏task,见6.1里判断可以分屏后,动画结束会调用
public void launchSplitTasks(Consumer<Boolean> callback) {
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
LogUtils.getShellShareableInstanceId();
launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
mStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
instanceIds.first);
}
public void launchTasks(int taskId1, @Nullable Intent intent1, int taskId2,
@Nullable Intent intent2, @StagePosition int stagePosition,
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
@Nullable InstanceId shellInstanceId) {
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
options1.setFreezeRecentTasksReordering();
}
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
//..
} else {
final RemoteSplitLaunchAnimationRunner animationRunner =
new RemoteSplitLaunchAnimationRunner(taskId1, taskId2, callback);
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
animationRunner, 300, 150,
ActivityThread.currentActivityThread().getApplicationThread());
//说明2个task都有旧的活动
if (intent1 == null && intent2 == null) {
mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
shellInstanceId);
} else if (intent2 == null) {
//intent1不为空,通过intent启动
launchIntentOrShortcutLegacy(intent1, options1, taskId2, stagePosition, splitRatio,
adapter, shellInstanceId);
} else if (intent1 == null) {
//intent2不为空,同样通过intent启动
launchIntentOrShortcutLegacy(intent2, options1, taskId1,
getOppositeStagePosition(stagePosition), splitRatio, adapter,
shellInstanceId);
} else {
//2个intent都不是空
mSystemUiProxy.startIntentsWithLegacyTransition(getPendingIntent(intent1),
options1.toBundle(), getPendingIntent(intent2), null /* options2 */,
stagePosition, splitRatio, adapter, shellInstanceId);
}
}
}
4.11.总结
- "split left/right"点击事件见4.7.2 之后走到 4.9.1设置数据,以及切换状态,数据最终给到4.10.1
- 之后需要点击分屏的第二个task,点击后走5.1再6.1,最后回到4.10.4开始加载两个task
>1.旧代码
- 这里看下我们的旧代码,看else,如果是快捷方式的话,就直接传递的Intent
- shortcut的Intent可以参考2.2.1,是不能直接用的,导致通过小节8,9,10最后找不到对应的activity
if (taskPendingIntent == null) {
mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
splitRatio, adapter);
} else {
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
stagePosition, splitRatio, adapter);
}
5.TaskView.java
recents页面里的task列表用的就是这个布局
public class TaskView extends FrameLayout implements Reusable {
5.1.confirmSecondSplitSelectApp
protected boolean confirmSecondSplitSelectApp() {
int index = getLastSelectedChildTaskIndex();
TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
if (container != null) {//见6.1
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView().getDrawable(), container.getThumbnailView(),
container.getThumbnailView().getThumbnail(), /* intent */ null);
}
return false;
}
有如下两处调用
>1.onClick
private void onClick(View view) {
if (getTask() == null) {
return;
}
if (confirmSecondSplitSelectApp()) {
//如果点击的是第二个分屏的话,返回
return;
}
//否则加载task
launchTasks();
}
>2.setIcon
protected void setIcon(IconView iconView, @Nullable Drawable icon) {
if (icon != null) {
iconView.setDrawable(icon);
iconView.setOnClickListener(v -> {
//上边的icon点击事件也是先判断这个
if (confirmSecondSplitSelectApp()) {
return;
}
showTaskMenu(iconView);
});
iconView.setOnLongClickListener(v -> {
requestDisallowInterceptTouchEvent(true);
return showTaskMenu(iconView);
});
} else {
iconView.setDrawable(null);
iconView.setOnClickListener(null);
iconView.setOnLongClickListener(null);
}
}
6.RecentsView.java
6.1.confirmSplitSelect
public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
View secondView, @Nullable Bitmap thumbnail, Intent intent) {
//见4.9.3,是否第一个task设置过
if (canLaunchFullscreenTask()) {
return false;
}
//见4.10.3,分屏的2个task数据都设置过了
if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
return true;
}
// Second task is selected either as an already-running Task or an Intent
if (task != null) {
if (!task.isDockable) {
// 不支持分屏
mSplitUnsupportedToast.show();
return true;
}
//设置第二个task
mSplitSelectStateController.setSecondTask(task);
} else {//或者设置intent
mSplitSelectStateController.setSecondTask(intent);
}
RectF secondTaskStartingBounds = new RectF();
Rect secondTaskEndingBounds = new Rect();
Rect firstTaskStartingBounds = new Rect();
Rect firstTaskEndingBounds = mTempRect;
boolean isTablet = mActivity.getDeviceProfile().isTablet;
SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());
int halfDividerSize = getResources()
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
//获取两个分屏task最终的范围
mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
secondTaskEndingBounds);
mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
false /* fadeWithThumbnail */, true /* isStagedTask */);
safeRemoveDragLayerView(mSecondFloatingTaskView);
mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, secondView,
thumbnail, drawable, secondTaskStartingBounds);
mSecondFloatingTaskView.setAlpha(1);
mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
timings.getInstructionsFadeStartOffset(),
timings.getInstructionsFadeEndOffset()));
pendingAnimation.addEndListener(aBoolean -> {
//动画结束后,开始加载2个分屏的task,见4.10.4
mSplitSelectStateController.launchSplitTasks(
aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
});
mSecondSplitHiddenView = containerTaskView;
if (mSecondSplitHiddenView != null) {
mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
}
// Fade out all other views underneath placeholders
ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
pendingAnimation.buildAnim().start();
return true;
}
6.2.findLastActiveTaskAndDoSplitOperation
public void findLastActiveTaskAndDoSplitOperation(ComponentName componentName,
Consumer<Task> callback) {
//循环当前已存在的task
mModel.getTasks(taskGroups -> {
Task lastActiveTask = null;
// 找到和提供的组件名一样的task
for (int i = taskGroups.size() - 1; i >= 0; i--) {
GroupTask groupTask = taskGroups.get(i);
Task task1 = groupTask.task1;
if (isInstanceOfComponent(task1, componentName)) {
lastActiveTask = task1;
break;
}
Task task2 = groupTask.task2;
if (isInstanceOfComponent(task2, componentName)) {
lastActiveTask = task2;
break;
}
}
//返回给回调。
callback.accept(lastActiveTask);
});
}
>1.isInstanceOfComponent
public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
if (task == null) {
return false;
}
//排除已经在分屏状态的task
if (mSplitHiddenTaskView != null && mSplitHiddenTaskView.getTask().equals(task)) {
return false;
}
return task.key.baseIntent.getComponent().equals(componentName);
}
6.3.initiateSplitSelect
第一个分屏数据的设置
public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
mSplitSelectSource = splitSelectSource;
//见4.10.1
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTask);
}
7.分屏菜单相关
- 根据横竖屏,手机还是平板,显示的分屏选项是不一样的,反正就是上下左右四种。
public interface PagedOrientationHandler {
PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
7.1.PortraitPagedViewHandler.java
主要看这个即可,基本用的就是这个。
>1.getSplitPositionOptions
public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
return Utilities.getSplitPositionOptions(dp);//补充2
}
>2.getSplitPositionOptions
public static List<SplitPositionOption> getSplitPositionOptions(
DeviceProfile dp) {
List<SplitPositionOption> options = new ArrayList<>();
// 平板横屏模式下,添加 左右或者上下
if (dp.isTablet && dp.isLandscape) {
options.add(new SplitPositionOption(
R.drawable.ic_split_left, R.string.split_screen_position_left,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
options.add(new SplitPositionOption(
R.drawable.ic_split_right, R.string.split_screen_position_right,
STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
} else {//手机或者竖屏模式
//手机反向横屏(逆时针转90度),添加 split right
if (dp.isSeascape()) {
// Add left/right options
options.add(new SplitPositionOption(
R.drawable.ic_split_right, R.string.split_screen_position_right,
STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
} else if (dp.isLandscape) {
//手机横屏(顺时针90度) 添加 split left
options.add(new SplitPositionOption(
R.drawable.ic_split_left, R.string.split_screen_position_left,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
} else {
// 竖屏,添加 split top
options.add(new SplitPositionOption(
R.drawable.ic_split_top, R.string.split_screen_position_top,
STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
}
}
return options;
}
7.2.RecentsOrientedState.java
recent页面,点击快照上边的logo,弹框里显示的split选项
>1.updateHandler
这里根据方向不同,选用不同的handler
private boolean updateHandler() {
//桌面可以旋转的话,返回的就是桌面旋转的角度,否则返回的是默认角度0
mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
//mTouchRotation就是旋转角度
if (mRecentsActivityRotation == mTouchRotation || isRecentsActivityRotationAllowed()) {
mOrientationHandler = PagedOrientationHandler.PORTRAIT;
} else if (mTouchRotation == ROTATION_90) {
mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
} else if (mTouchRotation == ROTATION_270) {
mOrientationHandler = PagedOrientationHandler.SEASCAPE;
} else {
mOrientationHandler = PagedOrientationHandler.PORTRAIT;
}
int oldStateId = mStateId;
// Each SurfaceRotation value takes two bits
mStateId = (((((mFlags << 2)
| mDisplayRotation) << 2)
| mTouchRotation) << 3)
| (mRecentsRotation < 0 ? 7 : mRecentsRotation);
return mStateId != oldStateId;
}
8.PackageManagerServiceUtils.java
8.1.applyEnforceIntentFilterMatching
- 2.2.1的第一行日志就在这个方法里显示的
- 这个方法就是对于给定的resolveInfo,进行interFilter的强制过滤判定
- 这个方法调用的地方,见小节9和小节10
public static void applyEnforceIntentFilterMatching(
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
Intent intent, String resolvedType, int filterCallingUid) {
for (int i = resolveInfos.size() - 1; i >= 0; --i) {
final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
// 当调用者是系统、root或相同的应用程序时,不强制过滤器匹配
if (ActivityManager.checkComponentPermission(null, filterCallingUid,
info.applicationInfo.uid, false) == PackageManager.PERMISSION_GRANTED) {
continue;
}
// Only enforce filter matching if target app's target SDK >= T
//也就是目标版本>=13的才进行判定
if (!compat.isChangeEnabledInternal(
ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
continue;
}
final ParsedMainComponent comp;
if (info instanceof ActivityInfo) {
//根据广播和活动获取不同的组件
if (isReceiver) {
comp = resolver.getReceiver(info.getComponentName());
} else {
comp = resolver.getActivity(info.getComponentName());
}
} else if (info instanceof ServiceInfo) {
//服务
comp = resolver.getService(info.getComponentName());
} else {
//
throw new IllegalArgumentException("Unsupported component type");
}
if (comp == null || comp.getIntents().isEmpty()) {
//没找到,或者意图为空,其实就是补充1里,intent-filter组成的Intent
continue;
}
boolean match = false;
//循环组件支持的所有intent-filter,看是否有匹配的
for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
//参数里传递来的intent以及resolvedType是否和我们查找到的intentFilter匹配
if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
match = true;
break;
}
}
if (!match) {
Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
Slog.w(TAG, "Access blocked: " + comp.getComponentName());
//没有匹配的,移除数据
resolveInfos.remove(i);
}
}
}
>1.intentFilter
一个组件可以有多个intent-filter,也就是可以有多种Intent来找到这个类
<activity
android:name=".TestActivity"
android:exported="true"
android:icon="@drawable/ic_homepage_display"
android:label="3-testActivity">
<intent-filter android:priority="1">
<action android:name="com.test.my3test" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
9.ComputerEngine.java
9.1.queryIntentActivitiesInternal
public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
int filterCallingUid, int userId, boolean resolveForStart,
boolean allowDynamicSplits) {
//userId不存在
if (!mUserManager.exists(userId)) return Collections.emptyList();
final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"query intent activities");
final String pkgName = intent.getPackage();
Intent originalIntent = null;
//获取组件名
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
}
flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
comp != null || pkgName != null /*onlyExposedExplicitly*/,
isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId, resolvedType,
flags));
List<ResolveInfo> list = Collections.emptyList();
boolean skipPostResolution = false;
if (comp != null) {
//组件名不为空,说明是显示的声明
final ActivityInfo ai = getActivityInfo(comp, flags, userId);
if (ai != null) {
// When specifying an explicit component, we prevent the activity from being
// used when either 1) the calling package is normal and the activity is within
// an ephemeral application or 2) the calling package is ephemeral and the
// activity is not visible to ephemeral applications.
//两种阻止意图的情况:
//1 > 调用包正常,启动的activity是在instant app里
//2 > 调用包是intant app,要启动的activity对instant app不可见。
final boolean matchInstantApp =
(flags & PackageManager.MATCH_INSTANT) != 0;
final boolean matchVisibleToInstantAppOnly =
(flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean matchExplicitlyVisibleOnly =
(flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
final boolean isCallerInstantApp =
instantAppPkgName != null;
final boolean isTargetSameInstantApp =
comp.getPackageName().equals(instantAppPkgName);
final boolean isTargetInstantApp =
(ai.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
final boolean isTargetVisibleToInstantApp =
(ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
final boolean isTargetExplicitlyVisibleToInstantApp =
isTargetVisibleToInstantApp
&& (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP)
== 0;
final boolean isTargetHiddenFromInstantApp =
!isTargetVisibleToInstantApp
|| (matchExplicitlyVisibleOnly
&& !isTargetExplicitlyVisibleToInstantApp);
//阻止instant的,情况1
final boolean blockInstantResolution =
!isTargetSameInstantApp
&& ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
|| (matchVisibleToInstantAppOnly && isCallerInstantApp
&& isTargetHiddenFromInstantApp));
//阻止正常的,情况2
final boolean blockNormalResolution =
!resolveForStart && !isTargetInstantApp && !isCallerInstantApp
&& shouldFilterApplication(
getPackageStateInternal(ai.applicationInfo.packageName,
Process.SYSTEM_UID), filterCallingUid, userId);
//排除情况1和2的,继续处理
if (!blockInstantResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list = new ArrayList<>(1);
list.add(ri);
//见3.1
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
list, false, intent, resolvedType, filterCallingUid);
}
}
} else {//隐式的意图,没有组件名的
//通过intent查找
QueryIntentActivitiesResult lockedResult =
queryIntentActivitiesInternalBody(
intent, resolvedType, flags, filterCallingUid, userId,
resolveForStart, allowDynamicSplits, pkgName, instantAppPkgName);
//结果有两种
if (lockedResult.answer != null) {
skipPostResolution = true;
list = lockedResult.answer;
} else {
//
list = lockedResult.result;
}
}
if (originalIntent != null) {
// We also have to ensure all components match the original intent
//见3.1
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
list, false, originalIntent, resolvedType, filterCallingUid);
}
//为true,直接使用list,为false,则过滤短暂活动的activity
return skipPostResolution ? list : applyPostResolutionFilter(
list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
resolveForStart, userId, intent);
}
9.2.queryIntentServicesInternal
和9.1差不多,这个是处理service的
public final @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
int callingUid, boolean includeInstantApps) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
enforceCrossUserOrProfilePermission(callingUid,
userId,
false /*requireFullPermission*/,
false /*checkShell*/,
"query intent receivers");
final String instantAppPkgName = getInstantAppPackageName(callingUid);
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
}
List<ResolveInfo> list = Collections.emptyList();
if (comp != null) {
final ServiceInfo si = getServiceInfo(comp, flags, userId);
if (si != null) {
// When specifying an explicit component, we prevent the service from being
// used when either 1) the service is in an instant application and the
// caller is not the same instant application or 2) the calling package is
// ephemeral and the activity is not visible to ephemeral applications.
final boolean matchInstantApp =
(flags & PackageManager.MATCH_INSTANT) != 0;
final boolean matchVisibleToInstantAppOnly =
(flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean isCallerInstantApp =
instantAppPkgName != null;
final boolean isTargetSameInstantApp =
comp.getPackageName().equals(instantAppPkgName);
final boolean isTargetInstantApp =
(si.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
final boolean isTargetHiddenFromInstantApp =
(si.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
final boolean blockInstantResolution =
!isTargetSameInstantApp
&& ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
|| (matchVisibleToInstantAppOnly && isCallerInstantApp
&& isTargetHiddenFromInstantApp));
final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
&& shouldFilterApplication(
getPackageStateInternal(si.applicationInfo.packageName,
Process.SYSTEM_UID), callingUid, userId);
if (!blockInstantResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
ri.serviceInfo = si;
list = new ArrayList<>(1);
list.add(ri);
//见3.1
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
list, false, intent, resolvedType, callingUid);
}
}
} else {
list = queryIntentServicesInternalBody(intent, resolvedType, flags,
userId, callingUid, instantAppPkgName);
}
if (originalIntent != null) {
// We also have to ensure all components match the original intent
//见3.1
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mInjector.getCompatibility(), mComponentResolver,
list, false, originalIntent, resolvedType, callingUid);
}
return list;
}
10.ResolveIntentHelper.java
10.1.queryIntentReceiversInternal
这个里和9.1的差不多
public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
int filterCallingUid, boolean forSend) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
// The identity used to filter the receiver components
final int queryingUid = forSend ? Process.SYSTEM_UID : filterCallingUid;
computer.enforceCrossUserPermission(queryingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers");
final String instantAppPkgName = computer.getInstantAppPackageName(queryingUid);
flags = computer.updateFlagsForResolve(flags, userId, queryingUid,
false /*includeInstantApps*/,
computer.isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId,
resolvedType, flags));
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
if (intent.getSelector() != null) {
originalIntent = intent;
intent = intent.getSelector();
comp = intent.getComponent();
}
}
final ComponentResolverApi componentResolver = computer.getComponentResolver();
List<ResolveInfo> list = Collections.emptyList();
if (comp != null) {
final ActivityInfo ai = computer.getReceiverInfo(comp, flags, userId);
if (ai != null) {
// When specifying an explicit component, we prevent the activity from being
// used when either 1) the calling package is normal and the activity is within
// an instant application or 2) the calling package is ephemeral and the
// activity is not visible to instant applications.
final boolean matchInstantApp =
(flags & PackageManager.MATCH_INSTANT) != 0;
final boolean matchVisibleToInstantAppOnly =
(flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean matchExplicitlyVisibleOnly =
(flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
final boolean isCallerInstantApp =
instantAppPkgName != null;
final boolean isTargetSameInstantApp =
comp.getPackageName().equals(instantAppPkgName);
final boolean isTargetInstantApp =
(ai.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
final boolean isTargetVisibleToInstantApp =
(ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
final boolean isTargetExplicitlyVisibleToInstantApp = isTargetVisibleToInstantApp
&& (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
final boolean isTargetHiddenFromInstantApp = !isTargetVisibleToInstantApp
|| (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
final boolean blockResolution =
!isTargetSameInstantApp
&& ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
|| (matchVisibleToInstantAppOnly && isCallerInstantApp
&& isTargetHiddenFromInstantApp));
if (!blockResolution) {
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list = new ArrayList<>(1);
list.add(ri);
//见8.1
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver, list, true, intent,
resolvedType, filterCallingUid);
}
}
} else {
String pkgName = intent.getPackage();
if (pkgName == null) {
final List<ResolveInfo> result = componentResolver
.queryReceivers(computer, intent, resolvedType, flags, userId);
if (result != null) {
list = result;
}
}
final AndroidPackage pkg = computer.getPackage(pkgName);
if (pkg != null) {
final List<ResolveInfo> result = componentResolver.queryReceivers(computer,
intent, resolvedType, flags, pkg.getReceivers(), userId);
if (result != null) {
list = result;
}
}
}
if (originalIntent != null) {
// We also have to ensure all components match the original intent
//见8.1
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
mPlatformCompat, componentResolver,
list, true, originalIntent, resolvedType, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
false, userId, intent);
}
11.StageCoordinator.java
最终分屏的逻辑会走到这个类里,里边有很多start方法
12.问题
合并了StageCoordinator里startShortcut相关的方法后,确实可以让快捷方式正常的显示分屏,可还是会时不时的出问题,
12.1.无法启动后台应用
>1.log
Background activity start [callingPackage: com.google.android.contacts; callingUid: 10127; appSwitchState: 2; isCallingUidForeground: false;
callingUidHasAnyVisibleWindow: false; callingUidProcState: CACHED_ACTIVITY; isCallingUidPersistentSystemProcess: false; realCallingUid: 1000;
isRealCallingUidForeground: false; realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: PERSISTENT; isRealCallingUidPersistentSystemProcess: true;
originatingPendingIntent: null; allowBackgroundActivityStart: false; intent:
Intent { act=android.intent.action.INSERT dat=content://com.android.contacts/... flg=0x1000c000
cmp=com.google.android.contacts/com.google.android.apps.contacts.editor.EditorShortcutSpringBoardActivity }; callerApp: null; inVisibleTask: false]
>2.shouldAbortBackgroundActivityStart
ActivityStarter.java
- 正常应该返回false的,从日志看,有时候不知道为啥不满足条件,走到了最后,返回个true,后台活动启动被中断了,分屏自然无法正常显示
- 弄了个暴力的方法,无视这个方法,都返回false,有点可怕,没敢提交代码。。不过确实基本碰不到无法分屏的情况
boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
final String callingPackage, int realCallingUid, int realCallingPid,
WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) {
>3.解决
- 最终还是建议禁掉桌面shortuct显示分屏选项,这样问题少点,要不合并这些代码问题太多。毕竟我们的代码太老了。
- 就是4.4.1里的代码,对参数shortcut集合数据进行过滤
- filter里增加条件 shortcut.mItemInfo instanceof WorkspaceItemInfo这种类型的就是shortuct
List<SystemShortcut> systemShortcuts = shortcuts .stream()
.filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets))
.collect(Collectors.toList());