MTK Android 14 锁屏通知栏与相机预览界面重叠

433 阅读7分钟

Bug触发前提和操作:

  • 设置为滑动解锁

  • 支持双击power按键跳转相机功能

  • 反复亮灭屏,并通过双击power按键唤醒相机就有几率触发此问题

Bug具体表现如下图:

keyguard壁纸图层消失,显示出了底下的camera预览界面,且当前keyguard时序错乱,解锁流程异常

分析流程:

因为是静态壁纸,所以最早的考虑可能和Systemui的LockscreenWallpaper.java 和NotificationMediaManager.java这两部分壁纸处理部可能有些问题,但通过调试验证后发现LockscreenWallpaper 默认的情况使用的是系统wallpaper图层,具体看下面loadBitmap方法注释;根据上述分析,尝试锁屏壁纸为null直接去取launcher的壁纸后测试,这个时候可以发现壁纸确实不会异常消失了,但是问题依然存在,解锁流程异常,上划解锁的时候会卡顿,有时候还要解锁两遍才行,所以这个问题并没有解决,还要继续分析,

//system/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
  
public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) {

        final int lockWallpaperUserId =
                selectedUser != null ? selectedUser.getIdentifier() : currentUserId;
        ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile(
                WallpaperManager.FLAG_LOCK, lockWallpaperUserId);

        //未单独设置锁屏壁纸时,锁屏壁纸为null,这里默认取的是桌面壁纸,这样NotificationMediaManager那边一直可以通过getBitmap正常取到壁纸
        if(fd == null){
            fd = mWallpaperManager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM,lockWallpaperUserId);
        }

        if (fd != null) {
 
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inPreferredConfig = Bitmap.Config.HARDWARE;
                return LoaderResult.success(BitmapFactory.decodeFileDescriptor(
                        fd.getFileDescriptor(), null, options));

        } else {
            if (selectedUser != null) {
                // Show the selected user's static wallpaper.
                return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
                        selectedUser.getIdentifier(), true /* hardware */));

            } else {
                //默认会走到这里,使用系统默认wallpaper图层,此时getBitmap一直为null
                // When there is no selected user, show the system wallpaper
                return LoaderResult.success(null);
            }
        }
    }

初步来看不是systemui 壁纸部分的逻辑问题,可以看看是否为壁纸图层问题,重新复现此问题后通过dump SurfaceFlinger读取图层,对比正常逻辑来看,异常的时候壁纸图层消失了,然后从抓取的log分析,systemui时序也有些问题,对比正常和异常的时序log如下,keyguardGoingAway 之后没有正常去执行handleStartKeyguardExitAnimation的退出动画流程,目前来看就是没有正常退出keyguard动画导致后续时序错乱,

正常流程: 

  36636: 01-14 17:57:04.877845  1450  1450 D KeyguardViewMediator: handleKeyguardDone
  36637: 01-14 17:57:04.877935  1450  1450 D KeyguardViewMediator: handleHide
  36638: 01-14 17:57:04.877965  1450  1450 D KeyguardViewMediator: keyguardGoingAway
  37048: 01-14 17:57:04.998568  1450  1450 D KeyguardViewMediator: handleStartKeyguardExitAnimation, the startTime=0 fadeoutDuration=0
  37053: 01-14 17:57:04.999293  1450  1450 D KeyguardViewMediator: set mKeyguardDoneOnGoing = false
  39240: 01-14 17:57:05.306755  1450  1450 D KeyguardViewMediator: onKeyguardExitRemoteAnimationFinished
  39398: 01-14 17:57:05.315843  1450  1450 D KeyguardViewMediator: onKeyguardExitFinished()
  39405: 01-14 17:57:05.316766  1450  1450 D KeyguardViewMediator: adjustStatusBarLocked: mShowing=false mOccluded=false isSecure=false force=false mPowerGestureIntercepted=false --> flags=0x0
  39407: 01-14 17:57:05.317367  1450  1450 D KeyguardViewMediator: onKeyguardExitRemoteAnimationFinished#hideKeyguardViewAfterRemoteAnimation
  39523: 01-14 17:57:05.348362  1450  1450 D KeyguardViewMediator: keyguardGone
  40071: 01-14 17:57:05.393523  1450  1804 D KeyguardViewMediator: updateActivityLockScreenState(false, false)
  42114: 01-14 17:57:05.705789  1450  7484 D KeyguardViewMediator: setOccluded(false)
  42115: 01-14 17:57:05.705812  1450  7484 D KeyguardViewMediator: setOccluded false

异常流程:

  49189: 01-14 17:57:07.474846  1450  1450 D KeyguardViewMediator: handleKeyguardDone
  49190: 01-14 17:57:07.474904  1450  1450 D KeyguardViewMediator: handleHide
  49191: 01-14 17:57:07.474943  1450  1450 D KeyguardViewMediator: keyguardGoingAway
  53185: 01-14 17:57:08.217943  1450  6811 D KeyguardViewMediator: setOccluded(false)
  53186: 01-14 17:57:08.217971  1450  6811 D KeyguardViewMediator: setOccluded false
  53187: 01-14 17:57:08.218512  1450  1450 D KeyguardViewMediator: handleSetOccluded(false)
  53188: 01-14 17:57:08.218645  1450  1450 D KeyguardViewMediator: isOccluded=false,mPowerGestureIntercepted=false
  57112: 01-14 17:57:09.654649  1450  1450 D KeyguardViewMediator: received DELAYED_KEYGUARD_ACTION with seq = 14, mDelayedShowingSequence = 16

继续加了一些log调试,通过对比正常和异常时候日志,可以发现正常过渡动画的流程,最终是会执行TRANSIT_TO_BACK的过渡动画的,异常的时候这个操作被丢失掉了,会卡在 TRANSIT_WAKE的动画,一直到TRANSIT_WAKE被abort;而TRANSIT_TO_BACK这个过渡动画就是KeyguardController的requestTransitionIfNeeded调用产生的,并且会让壁纸可见(和异常时候表现一致,大致确定是这个动画没有正常执行的问题了)

// system/frameworks/base/services/core/java/com/android/server/wm/KeyguardController.java
void keyguardGoingAway(int displayId, int flags) {
        ...
        final int transitFlags = convertTransitFlags(flags);
        final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
        dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
        // We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
        // TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
        // away.
        dc.mAtmService.getTransitionController().requestTransitionIfNeeded(
                TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc);
        updateKeyguardSleepToken();
        // Make the home wallpaper visible
        dc.mWallpaperController.showWallpaperInTransition(true /* showHome */);
        // Some stack visibility might change (e.g. docked stack)
        mRootWindowContainer.resumeFocusedTasksTopActivities();
        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
        mRootWindowContainer.addStartingWindowsForVisibleActivities();
        mWindowManager.executeAppTransition();
    } finally {
        mService.continueWindowLayout();
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
}

继续调试,可以知道是isCollecting为true导致无法被正常创建导致;最开始尝试过滤TRANSIT_TO_BACK来调试,但后续的createTransition的有做mTransitionPlayer和mCollectingTransition 的是否为null做判断,修改对逻辑影响太大,遂放弃;继续看Transition的操作逻辑和调试的log, mCollectingTransition的collectting有超时机制,而Transition最终会去执行TransitionController.onAbort中断当前的Transition,

// system/frameworks/base/services/core/java/com/android/server/wm/TransitionController.java
Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
      ... ) {
    if (mTransitionPlayer == null) {
        return null;
    }
    Transition newTransition = null;
    if (isCollecting()) {
        ...
    } else {
        newTransition = requestStartTransition(createTransition(type, flags),
                trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
        if (newTransition != null && displayChange != null && trigger != null
                && trigger.asDisplayContent() != null) {
            setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
        }
    }
    ...
    return newTransition;
}


/**
* @return {@code true} if transition is actively collecting changes. This is {@code false}
* once a transition is playing
*/
boolean isCollecting() {
    return mCollectingTransition != null;
}

/** Called when a transition is aborted. This should only be called by {@link Transition} */
void onAbort(Transition transition) {
    if (transition != mCollectingTransition) {
        int waitingIdx = mWaitingTransitions.indexOf(transition);
        if (waitingIdx < 0) {
            throw new IllegalStateException("Too late for abort.");
        }
        mWaitingTransitions.remove(waitingIdx);
    } else {
        mCollectingTransition = null;
        if (!mWaitingTransitions.isEmpty()) {
            mCollectingTransition = mWaitingTransitions.remove(0);
        }
        if (mCollectingTransition == null) {
            // nothing collecting anymore, so clear order records.
            mLatestOnTopTasksReported.clear();
        }
    }
    // This is called during Transition.abort whose codepath will eventually check the queue
    // via sync-engine idle.
}

有了上述两点分析,而且TRANSIT_TO_BACK 是正常解锁后最终Transition动画,所以可以尝试针对TRANSIT_TO_BACK创建失败时候做保存操作,在上个Transition被Abort了之后重新去走TRANSIT_TO_BACK的创建流程,具体修改如下代码,然后再次测试验证ok,算是暂时修复了此问题

// system/frameworks/base/services/core/java/com/android/server/wm/TransitionController.java   
+  private int waitType;
+  private int waitFlag;
+  private WindowContainer waitTrigger;
+  private WindowContainer waitReadyGroupRef

    @Nullable
    Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
            @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
            @NonNull WindowContainer readyGroupRef) {
-        return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef,
+        // BEGIN <When the TRANSIT_TO_BACK animation fails to be added, it will be re-executed after the collection is completed.>
+        Transition  mTransition = requestTransitionIfNeeded(type, flags, trigger, readyGroupRef,
                null /* remoteTransition */, null /* displayChange */);
+        if (mTransition == null && type == TRANSIT_TO_BACK) {
+            waitType = type;
+            waitFlag = flags;
+            waitTrigger = trigger;
+            waitReadyGroupRef = readyGroupRef;
+            Log.e(TAG,"Add TRANSIT_TO_BACK transition until collecting finish");
+        }
+        return mTransition;
+        // END <When the TRANSIT_TO_BACK animation fails to be added, it will be re-executed after the collection is completed.> 
    }
                                          
    /** Called when a transition is aborted. This should only be called by {@link Transition} */
    void onAbort(Transition transition) {
        if (transition != mCollectingTransition) {
            int waitingIdx = mWaitingTransitions.indexOf(transition);
            if (waitingIdx < 0) {
                throw new IllegalStateException("Too late for abort.");
            }
            mWaitingTransitions.remove(waitingIdx);
        } else {
            mCollectingTransition = null;
            if (!mWaitingTransitions.isEmpty()) {
                mCollectingTransition = mWaitingTransitions.remove(0);
            }
            if (mCollectingTransition == null) {
                // nothing collecting anymore, so clear order records.
                mLatestOnTopTasksReported.clear();
            }
+            // BEGIN <When the TRANSIT_TO_BACK animation fails to be added, it will be re-executed after the collection is completed.> 
+            if (waitType == TRANSIT_TO_BACK && mCollectingTransition == null) {
+                Log.e(TAG,"come to here continue play animation");
+                waitType = -1;
+                requestTransitionIfNeeded(waitType,waitFlag,waitTrigger,waitReadyGroupRef);
+            }
+            // END <When the TRANSIT_TO_BACK animation fails to be added, it will be re-executed after the collection is completed.> 
        }
        // This is called during Transition.abort whose codepath will eventually check the queue
        // via sync-engine idle.
    }

总结:

最后总结一下,这个Keyguard壁纸图层消失的问题,主要还是亮灭屏太快,framework的动画流程出现问题,前面一个Transition还没有collecting执行完毕,就执行到了TRANSIT_TO_BACK的过渡,导致过渡动画被废弃无法正常执行,目前的方案是把前面废弃的动画TRANSIT_TO_BACK重新执行,暂时解决了这个问题,另外附上调试捋出来的keyguard退出动画完整的创建过渡动画和调用的流程

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀