ステーション b での無料ビデオチュートリアル説明:
https://www.bilibili.com/video/BV1wj411o7A9/
pipウィンドウの移動開始処理に削除ボタン表示処理があります。
DismissTarget 呼び出しスタックを表示します。
showDismissTargetMaybe:287, PipDismissTargetHandler (com.android.wm.shell.pip.phone)
onMove:828, PipTouchHandler$DefaultPipTouchGesture (com.android.wm.shell.pip.phone)
handleTouchEvent:575, PipTouchHandler (com.android.wm.shell.pip.phone)
onInputEvent:-1, PipController$$ExternalSyntheticLambda5 (com.android.wm.shell.pip.phone)
onInputEvent:76, PipInputConsumer$InputEventReceiver (com.android.wm.shell.pip.phone)
dispatchInputEvent:267, InputEventReceiver (android.view)
nativeConsumeBatchedInputEvents:-1, InputEventReceiver (android.view)
consumeBatchedInputEvents:247, InputEventReceiver (android.view)
doConsumeBatchedInput:84, BatchedInputEventReceiver (android.view)
run:113, BatchedInputEventReceiver$BatchedInputRunnable (android.view)
run:1231, Choreographer$CallbackRecord (android.view)
run:1239, Choreographer$CallbackRecord (android.view)
doCallbacks:899, Choreographer (android.view)
doFrame:824, Choreographer (android.view)
run:1214, Choreographer$FrameDisplayEventReceiver (android.view)
handleCallback:942, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:201, Looper (android.os)
loop:288, Looper (android.os)
main:7898, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:548, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:936, ZygoteInit (com.android.internal.os)
DismissTarget を作成するための関連メソッド:
public void createOrUpdateDismissTarget() {
if (!mTargetViewContainer.isAttachedToWindow()) {
mTargetViewContainer.cancelAnimators();
mTargetViewContainer.setVisibility(View.INVISIBLE);
mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
mHasDismissTargetSurface = false;
try {
mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
} catch (IllegalStateException e) {
// This shouldn't happen, but if the target is already added, just update its layout
// params.
mWindowManager.updateViewLayout(
mTargetViewContainer, getDismissTargetLayoutParams());
}
} else {
mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
}
}
pip ウィンドウのタッチ イベントの受け入れ部分の分析:
上記の削除ボタン ウィンドウの表示スタックから、pip ウィンドウのタッチ イベント送信が次のように行われていることがわかります。
onInputEvent:-1, PipController$$ExternalSyntheticLambda5 (com.android.wm.shell.pip.phone)
onInputEvent:76, PipInputConsumer$InputEventReceiver (com.android.wm.shell.pip.phone)
dispatchInputEvent:267, InputEventReceiver (android.view)
実際、コアは PipInputConsumer クラスです。ここで PipInputConsumer を詳細に分析できます。
タッチ イベントを受け入れる PipInputConsumer の機能の中核は、次の登録メソッドを呼び出すことです。
public void registerInputConsumer() {
if (mInputEventReceiver != null) {
return;
}
final InputChannel inputChannel = new InputChannel();//构建对应的InputChannel
try {
// TODO(b/113087003): Support Picture-in-picture in multi-display.
mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
//调用到wms的createInputConsumer方法
mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
} catch (RemoteException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to create input consumer, %s", TAG, e);
}
mMainExecutor.execute(() -> {
mInputEventReceiver = new InputEventReceiver(inputChannel,
Looper.myLooper(), Choreographer.getSfInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
});
}
次に、createInputConsumer に注目します。
public void createInputConsumer(IBinder token, String name, int displayId,
InputChannel inputChannel) {
synchronized (mGlobalLock) {
DisplayContent display = mRoot.getDisplayContent(displayId);
if (display != null) {
display.getInputMonitor().createInputConsumer(token, name, inputChannel,
Binder.getCallingPid(), Binder.getCallingUserHandle());
}
}
}
//有调用到了InputMonitor的createInputConsumer
void createInputConsumer(IBinder token, String name, InputChannel inputChannel, int clientPid,
UserHandle clientUser) {
//这个地方是核心关键,会创建对应的InputConsumerImpl
final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
inputChannel, clientPid, clientUser, mDisplayId);
switch (name) {
case INPUT_CONSUMER_WALLPAPER:
break;
case INPUT_CONSUMER_PIP:
break;
case INPUT_CONSUMER_RECENTS_ANIMATION:
consumer.mWindowHandle.inputConfig &= ~InputConfig.NOT_FOCUSABLE;
break;
}
//添加这个name为INPUT_CONSUMER_PIP的Consumer
addInputConsumer(name, consumer);
}
//下面重点分析一下InputConsumerImpl这个构造
InputConsumerImpl(WindowManagerService service, IBinder token, String name,
InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) {
//调用InputManager创建相关的inputchannel,而且把inputchannel拷贝回systemui构造的inputchannel,这样inputchannel就可以通讯接受事件了
mClientChannel = mService.mInputManager.createInputChannel(name);
if (inputChannel != null) {
mClientChannel.copyTo(inputChannel);
}
//省略
}
要約すると、pip の touch イベントは実際には独立した入力チャネルを使用してそれ自体で受け入れられ、pip のアクティビティのウィンドウ入力チャネルとは何の関係もないことは明らかです。
PIP ウィンドウを手でドラッグして移動すると、次のようになります。
実際、関連する動きはすべてリーシュ層であり、関連する pip タスクの境界の更新とは関係ありません。
scheduleUserResizePip:1235, PipTaskOrganizer (com.android.wm.shell.pip)
scheduleUserResizePip:1212, PipTaskOrganizer (com.android.wm.shell.pip)
movePip:278, PipMotionHelper (com.android.wm.shell.pip.phone)
onMove:845, PipTouchHandler$DefaultPipTouchGesture (com.android.wm.shell.pip.phone)
handleTouchEvent:575, PipTouchHandler (com.android.wm.shell.pip.phone)
onInputEvent:-1, PipController$$ExternalSyntheticLambda5 (com.android.wm.shell.pip.phone)
onInputEvent:76, PipInputConsumer$InputEventReceiver (com.android.wm.shell.pip.phone)
dispatchInputEvent:267, InputEventReceiver (android.view)
nativeConsumeBatchedInputEvents:-1, InputEventReceiver (android.view)
consumeBatchedInputEvents:247, InputEventReceiver (android.view)
doConsumeBatchedInput:84, BatchedInputEventReceiver (android.view)
run:113, BatchedInputEventReceiver$BatchedInputRunnable (android.view)
run:1231, Choreographer$CallbackRecord (android.view)
run:1239, Choreographer$CallbackRecord (android.view)
doCallbacks:899, Choreographer (android.view)
doFrame:824, Choreographer (android.view)
run:1214, Choreographer$FrameDisplayEventReceiver (android.view)
handleCallback:942, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:201, Looper (android.os)
loop:288, Looper (android.os)
main:7898, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:548, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:936, ZygoteInit (com.android.internal.os)
関連するモバイル更新コード:
public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees,
Consumer<Rect> updateBoundsCallback) {
//省略
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
//核心关键点就是对mLeash在scale方法中进行相关的图层位置进行更新
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
if (mPipMenuController.isMenuVisible()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
}
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
}
//scale方法
public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds, float degrees) {
mTmpSourceRectF.set(sourceBounds);
mTmpSourceRectF.offsetTo(0, 0);
mTmpDestinationRectF.set(destinationBounds);
mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
mTmpTransform.postRotate(degrees,
mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY());
//对图层进行相关的matrix进行设置,这里面就是相关的偏移等
tx.setMatrix(leash, mTmpTransform, mTmpFloat9);
return this;
}
放した後、それを分析しましょう。実際にタスクの境界に関連するスタックを設定します。
放した後、単純なピップ ウィンドウの移動アニメーションが実行されます。
startBoundsAnimator:614, PipMotionHelper (com.android.wm.shell.pip.phone)
movetoTarget:451, PipMotionHelper (com.android.wm.shell.pip.phone)
flingToSnapTarget:405, PipMotionHelper (com.android.wm.shell.pip.phone)
onUp:889, PipTouchHandler$DefaultPipTouchGesture (com.android.wm.shell.pip.phone)
handleTouchEvent:587, PipTouchHandler (com.android.wm.shell.pip.phone)
onInputEvent:-1, PipController$$ExternalSyntheticLambda5 (com.android.wm.shell.pip.phone)
onInputEvent:76, PipInputConsumer$InputEventReceiver (com.android.wm.shell.pip.phone)
dispatchInputEvent:267, InputEventReceiver (android.view)
nativePollOnce:-1, MessageQueue (android.os)
next:335, MessageQueue (android.os)
loopOnce:161, Looper (android.os)
loop:288, Looper (android.os)
main:7898, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:548, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:936, ZygoteInit (com.android.internal.os)
アニメーションが再生されたら、関連する境界を設定します。
07-02 16:09:40.678 748 748 I lsm11 : applyTransaction 1 t = WindowContainerTransaction {
changes = {
android.os.BinderProxy@f5e9dbd={
bounds:Rect(61, 690 - 839, 1068),hasBoundsTransaction,}} hops = [] errorCallbackToken=null taskFragmentOrganizer=null }
07-02 16:09:40.678 748 748 I lsm11 : java.lang.Exception
07-02 16:09:40.678 748 748 I lsm11 : at android.window.WindowOrganizer.applyTransaction(WindowOrganizer.java:53)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.PipTaskOrganizer.applyFinishBoundsResize(PipTaskOrganizer.java:1436)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.PipTaskOrganizer.finishResize(PipTaskOrganizer.java:1383)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.PipTaskOrganizer.scheduleFinishResizePip(PipTaskOrganizer.java:1279)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.PipTaskOrganizer.scheduleFinishResizePip(PipTaskOrganizer.java:1261)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.PipTaskOrganizer.scheduleFinishResizePip(PipTaskOrganizer.java:1253)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.phone.PipMotionHelper.onBoundsPhysicsAnimationEnd(PipMotionHelper.java:657)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.phone.PipMotionHelper.$r8$lambda$QFpQr4PSFRGfS8YBsx6HKEKo4u4(Unknown Source:0)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.phone.PipMotionHelper$$ExternalSyntheticLambda4.run(Unknown Source:2)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.animation.PhysicsAnimator$withEndActions$1$1.invoke(PhysicsAnimator.kt:445)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.animation.PhysicsAnimator$withEndActions$1$1.invoke(PhysicsAnimator.kt:445)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.animation.PhysicsAnimator$InternalListener.onInternalAnimationEnd$frameworks__base__libs__WindowManager__Shell__android_common__WindowManager_Shell(PhysicsAnimator.kt:776)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.animation.PhysicsAnimator$configureDynamicAnimation$2$1.invoke(PhysicsAnimator.kt:672)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.animation.PhysicsAnimator$configureDynamicAnimation$2$1.invoke(PhysicsAnimator.kt:671)
07-02 16:09:40.678 748 748 I lsm11 : at kotlin.collections.CollectionsKt__MutableCollectionsKt.filterInPlace$CollectionsKt__MutableCollectionsKt(MutableCollections.kt:285)
07-02 16:09:40.678 748 748 I lsm11 : at kotlin.collections.CollectionsKt__MutableCollectionsKt.removeAll(MutableCollections.kt:269)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.animation.PhysicsAnimator$configureDynamicAnimation$2.onAnimationEnd(PhysicsAnimator.kt:671)
07-02 16:09:40.678 748 748 I lsm11 : at androidx.dynamicanimation.animation.DynamicAnimation.endAnimationInternal(DynamicAnimation.java:715)
07-02 16:09:40.678 748 748 I lsm11 : at androidx.dynamicanimation.animation.DynamicAnimation.doAnimationFrame(DynamicAnimation.java:690)
07-02 16:09:40.678 748 748 I lsm11 : at androidx.dynamicanimation.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:170)
07-02 16:09:40.678 748 748 I lsm11 : at androidx.dynamicanimation.animation.AnimationHandler$AnimationCallbackDispatcher.dispatchAnimationFrame(AnimationHandler.java:71)
07-02 16:09:40.678 748 748 I lsm11 : at androidx.dynamicanimation.animation.AnimationHandler.lambda$new$0(AnimationHandler.java:93)
07-02 16:09:40.678 748 748 I lsm11 : at androidx.dynamicanimation.animation.AnimationHandler.$r8$lambda$YiBk3K09ifLdAPQDzrOxNk7Tzy0(Unknown Source:0)
07-02 16:09:40.678 748 748 I lsm11 : at androidx.dynamicanimation.animation.AnimationHandler$$ExternalSyntheticLambda0.run(Unknown Source:2)
07-02 16:09:40.678 748 748 I lsm11 : at com.android.wm.shell.pip.phone.PipMotionHelper$1.lambda$postFrameCallback$0(PipMotionHelper.java:100)
pip ウィンドウは、アニメーション部分を削除するための削除ボタンの近くにあります。
関連するコールスタック
animateIntoDismissTarget:301, PipMotionHelper (com.android.wm.shell.pip.phone)
lambda$init$1:126, PipDismissTargetHandler (com.android.wm.shell.pip.phone)
$r8$lambda$7QUxuWTiiuYb4BpTVK2nS5TXgZA:-1, PipDismissTargetHandler (com.android.wm.shell.pip.phone)
invoke:-1, PipDismissTargetHandler$$ExternalSyntheticLambda1 (com.android.wm.shell.pip.phone)
maybeConsumeMotionEvent:390, MagnetizedObject (com.android.wm.shell.common.magnetictarget)
maybeConsumeMotionEvent:181, PipDismissTargetHandler (com.android.wm.shell.pip.phone)
handleTouchEvent:549, PipTouchHandler (com.android.wm.shell.pip.phone)
onInputEvent:-1, PipController$$ExternalSyntheticLambda5 (com.android.wm.shell.pip.phone)
onInputEvent:76, PipInputConsumer$InputEventReceiver (com.android.wm.shell.pip.phone)
dispatchInputEvent:267, InputEventReceiver (android.view)
nativeConsumeBatchedInputEvents:-1, InputEventReceiver (android.view)
consumeBatchedInputEvents:247, InputEventReceiver (android.view)
doConsumeBatchedInput:84, BatchedInputEventReceiver (android.view)
run:113, BatchedInputEventReceiver$BatchedInputRunnable (android.view)
run:1231, Choreographer$CallbackRecord (android.view)
run:1239, Choreographer$CallbackRecord (android.view)
doCallbacks:899, Choreographer (android.view)
doFrame:824, Choreographer (android.view)
run:1214, Choreographer$FrameDisplayEventReceiver (android.view)
handleCallback:942, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:201, Looper (android.os)
loop:288, Looper (android.os)
main:7898, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:548, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:936, ZygoteInit (com.android.internal.os)
対応するピップウィンドウ縮小アニメーションメソッド:
/** Animates the PIP into the dismiss target, scaling it down. */
void animateIntoDismissTarget(
MagnetizedObject.MagneticTarget target,
float velX, float velY,
boolean flung, Function0<Unit> after) {
final PointF targetCenter = target.getCenterOnScreen();
// PIP should fit in the circle
final float dismissCircleSize = mContext.getResources().getDimensionPixelSize(
R.dimen.dismiss_circle_size);
final float width = getBounds().width();
final float height = getBounds().height();
final float ratio = width / height;
// Width should be a little smaller than the circle size.
final float desiredWidth = dismissCircleSize * DISMISS_CIRCLE_PERCENT;
final float desiredHeight = desiredWidth / ratio;
final float destinationX = targetCenter.x - (desiredWidth / 2f);
final float destinationY = targetCenter.y - (desiredHeight / 2f);
// If we're already in the dismiss target area, then there won't be a move to set the
// temporary bounds, so just initialize it to the current bounds.
if (!mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
}
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_X, destinationX, velX, mAnimateToDismissSpringConfig)
.spring(FloatProperties.RECT_Y, destinationY, velY, mAnimateToDismissSpringConfig)
.spring(FloatProperties.RECT_WIDTH, desiredWidth, mAnimateToDismissSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, desiredHeight, mAnimateToDismissSpringConfig)
.withEndActions(after);
//直接启动动画
startBoundsAnimator(destinationX, destinationY);
}
ウィンドウ アイコンのアニメーション キー条件を削除する場合は、おそらくConsumeMotionEvent:
fun maybeConsumeMotionEvent(ev: MotionEvent): Boolean {
//省略
val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull {
target ->
//当前motionevent的x,y和删除按钮中心x,y距离计算是否已经小于阈值
val distanceFromTargetCenter = hypot(
ev.rawX - target.centerOnScreen.x,
ev.rawY - target.centerOnScreen.y)
distanceFromTargetCenter < target.magneticFieldRadiusPx
}
// If we aren't currently stuck to a target, and we're in the magnetic field of a target,
// we're newly stuck.
val objectNewlyStuckToTarget =
!objectStuckToTarget && targetObjectIsInMagneticFieldOf != null
// If we are currently stuck to a target, we're in the magnetic field of a target, and that
// target isn't the one we're currently stuck to, then touch events have moved into a
// adjacent target's magnetic field.
val objectMovedIntoDifferentTarget =
objectStuckToTarget &&
targetObjectIsInMagneticFieldOf != null &&
targetObjectIsStuckTo != targetObjectIsInMagneticFieldOf
if (objectNewlyStuckToTarget || objectMovedIntoDifferentTarget) {
velocityTracker.computeCurrentVelocity(1000)
val velX = velocityTracker.xVelocity
val velY = velocityTracker.yVelocity
// If the object is moving too quickly within the magnetic field, do not stick it. This
// only applies to objects newly stuck to a target. If the object is moved into a new
// target, it wasn't moving at all (since it was stuck to the previous one).
if (objectNewlyStuckToTarget && abs(velX) > stickToTargetMaxXVelocity) {
return false
}
// This touch event is newly within the magnetic field - let the listener know, and
// animate sticking to the magnet.
targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
cancelAnimations()
magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null)
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
}
//省略
return objectStuckToTarget // Always consume touch events if the object is stuck.
}