PipMenuView Principle Explanation - Android framework system development for vehicle-mounted mobile phones

Let's first understand what the PipMenuView is:

Free video tutorial explanation at station b:
https://www.bilibili.com/video/BV1wj411o7A9/
insert image description here

Under normal circumstances, pip displays
insert image description here
After touching the small window pip:
insert image description here
Did you find that there are 3 more buttons, but what is this window?
This person wanted to know who this picture was, but found that dumpsys could not see the specific window, and finally found its name through the SurfaceFlinger layer:
insert image description here
after knowing the name, you can start looking for it.

First look at the corresponding PipMenuView creation call stack

05-24 15:29:53.270   753   753 I PipDemo : attachPipMenuView
05-24 15:29:53.270   753   753 I PipDemo : java.lang.Exception
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.pip.phone.PhonePipMenuController.attachPipMenuView(PhonePipMenuController.java:187)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.pip.phone.PhonePipMenuController.attach(PhonePipMenuController.java:164)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.pip.PipTaskOrganizer.onTaskAppeared(PipTaskOrganizer.java:627)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.ShellTaskOrganizer.updateTaskListenerIfNeeded(ShellTaskOrganizer.java:555)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(ShellTaskOrganizer.java:465)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6$android-window-TaskOrganizer$1(TaskOrganizer.java:316)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Handler.handleCallback(Handler.java:942)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Handler.dispatchMessage(Handler.java:99)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Looper.loopOnce(Looper.java:201)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Looper.loop(Looper.java:288)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.app.ActivityThread.main(ActivityThread.java:7897)
05-24 15:29:53.270   753   753 I PipDemo : 	at java.lang.reflect.Method.invoke(Native Method)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:937)

Look at the corresponding window to add code:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java

void attachPipMenuView() {
    
    
        // In case detach was not called (e.g. PIP unexpectedly closed)
        if (mPipMenuView != null) {
    
    
            detachPipMenuView();
        }
        mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
                mSplitScreenController, mPipUiEventLogger);
                //注意这里调用是mSystemWindows的addView,和正常的WindowGlobal不一样哈
        mSystemWindows.addView(mPipMenuView,
                getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                0, SHELL_ROOT_LAYER_PIP);
        setShellRootAccessibilityWindow();
        // Make sure the initial actions are set
        updateMenuActions();
    }
    
    frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
     /**
     * Adds a view to system-ui window management.
     */
    public void addView(View view, WindowManager.LayoutParams attrs, int displayId,
            @WindowManager.ShellRootLayer int shellRootLayer) {
    
    
        PerDisplay pd = mPerDisplay.get(displayId);
        if (pd == null) {
    
    
            pd = new PerDisplay(displayId);
            mPerDisplay.put(displayId, pd);
        }
        pd.addView(view, attrs, shellRootLayer);
    }

Here it is found that addView of PerDisplay is called

private class PerDisplay {
    
    
        final int mDisplayId;
        private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();

        PerDisplay(int displayId) {
    
    
            mDisplayId = displayId;
        }

        public void addView(View view, WindowManager.LayoutParams attrs,
                @WindowManager.ShellRootLayer int shellRootLayer) {
    
    
            SysUiWindowManager wwm = addRoot(shellRootLayer);//根据shellRootLayer为SHELL_ROOT_LAYER_PIP创建出对应的SysUiWindowManager,SysUiWindowManager继承WindowlessWindowManager名字即可以看出这个东西其实没有windowstate的,WindowlessWindowManager实现了IWindowSession,这里session就和以前的session接口实现完全不一样,以前都是一般直接调用wms的接口,这里全部进行了重写,这里也就说明的为啥一定要通过session而不是直接调用wms接口了,就是为了这种动态扩展
           
            final Display display = mDisplayController.getDisplay(mDisplayId);
            //构造对应的SurfaceControlViewHost,会构造出对应的Viewrootimpl
            SurfaceControlViewHost viewRoot =
                    new SurfaceControlViewHost(
                            view.getContext(), display, wwm, true /* useSfChoreographer */);
            attrs.flags |= FLAG_HARDWARE_ACCELERATED;
            viewRoot.setView(view, attrs);//这里调用了SurfaceControlViewHost的setView,其实也最后会调用ViewRootImpl的setView
            mViewRoots.put(view, viewRoot);
            setShellRootAccessibilityWindow(shellRootLayer, view);
        }
  SysUiWindowManager addRoot(@WindowManager.ShellRootLayer int shellRootLayer) {
    
    
            SysUiWindowManager wwm = mWwms.get(shellRootLayer);
            if (wwm != null) {
    
    
                return wwm;
            }
            SurfaceControl rootSurface = null;
            ContainerWindow win = new ContainerWindow();//这里直接new的一个IWindow的binder对象进行添加到wms
            try {
    
    
                //获取一个SurfaceControl来放置pipmenuview,注意这里和不一样,以前都是wms帮忙创建对应的SurfaceControl,所以这里就是为啥dumpsys window没办法看到这里的PipMenuView的窗口
                rootSurface = mWmService.addShellRoot(mDisplayId, win, shellRootLayer);
            } catch (RemoteException e) {
    
    
            }
            //通过上面的rootSurface等构造出对应的SysUiWindowManager
            wwm = new SysUiWindowManager(mDisplayId, displayContext, rootSurface, win);
            mWwms.put(shellRootLayer, wwm);
            return wwm;
        }

Look at addShellRoot on the wms side:

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
    @Override
    public SurfaceControl addShellRoot(int displayId, IWindow client,
            @WindowManager.ShellRootLayer int shellRootLayer) {
    
    //最后目的就是获取一个可以挂载pipmenuview的SurfaceControl
      
        try {
    
    
            synchronized (mGlobalLock) {
    
    
                final DisplayContent dc = mRoot.getDisplayContent(displayId);
                if (dc == null) {
    
    
                    return null;
                }
                return dc.addShellRoot(client, shellRootLayer);
            }
        }
    }
    //frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
       SurfaceControl addShellRoot(@NonNull IWindow client,
            @WindowManager.ShellRootLayer int shellRootLayer) {
    
    
        ShellRoot root = mShellRoots.get(shellRootLayer);
       //省略
        root = new ShellRoot(client, this, shellRootLayer);//创建一个ShellRoot
        SurfaceControl rootLeash = root.getSurfaceControl();//获取一个root的SurfaceControl
         //省略
        mShellRoots.put(shellRootLayer, root);
        SurfaceControl out = new SurfaceControl(rootLeash, "DisplayContent.addShellRoot");
        return out;//把rootLeash的备份返回
    }
    //frameworks/base/services/core/java/com/android/server/wm/ShellRoot.java
ShellRoot(@NonNull IWindow client, @NonNull DisplayContent dc,
            @WindowManager.ShellRootLayer final int shellRootLayer) {
    
    
     //省略
        mClient = client;
        switch (shellRootLayer) {
    
    
            case SHELL_ROOT_LAYER_DIVIDER:
                mWindowType = TYPE_DOCK_DIVIDER;
                break;
            case SHELL_ROOT_LAYER_PIP:
                mWindowType = TYPE_APPLICATION_OVERLAY;//给出对应的window type
                break;
            default:
                throw new IllegalArgumentException(shellRootLayer
                        + " is not an acceptable shell root layer.");
        }
        //这里看到熟悉的创建了对应的windowtoken,因为有TYPE_APPLICATION_OVERLAY可以挂到层级结构树上面
        mToken = new WindowToken.Builder(dc.mWmService, client.asBinder(), mWindowType)
                .setDisplayContent(dc)
                .setPersistOnEmpty(true)
                .setOwnerCanManageAppTokens(true)
                .build();
         //这里以windowtoken为父亲创建了一个图层Shell Root Leash
        mSurfaceControl = mToken.makeChildSurface(null)
                .setContainerLayer()
                .setName("Shell Root Leash " + dc.getDisplayId())
                .setCallsite("ShellRoot")
                .build();
        mToken.getPendingTransaction().show(mSurfaceControl);
    }
    //frameworks/base/core/java/android/view/SurfaceControlViewHost.java
 /** @hide */
    public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
            @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
    
    
        mWm = wwm;
        mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer);//创建出对应的ViewRootImpl
        addConfigCallback(c, d);//添加相关configcallback

        WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);//注意在调用是addWindowlessRoot哈
//省略
    }

The above code has made it clear that ViewRootImpl has set the corresponding PipMenuView to ViewRootImpl, but it seems that the PipMenuView layer has not been added to the SurfaceFlinger layer. In fact, the core is still in ViewRootImpl

The process of adding the PipMenuView layer to the SurfaceFlinger layer

Did the setView of ViewRootImpl have a

 res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                            mTempControls);

Note that the mWindowSession here is already our WindowlessWindowManager, which has a corresponding rewrite here:


 public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
    
    
        return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
                outInputChannel, outInsetsState, outActiveControls);
    }
    @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
    
    
            //注意这里就是创建了一个画面图层名字就是PipMenuView
        final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
                .setFormat(attrs.format)
                .setBLASTLayer()
                .setName(attrs.getTitle().toString())
                .setCallsite("WindowlessWindowManager.addToDisplay");
        attachToParentSurface(window, b);//把PipMenuView图层挂到前面说的rootSurface
        final SurfaceControl sc = b.build();
  //省略
    }
      protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
    
    
        b.setParent(mRootSurface);
    }

The above has already explained clearly what is going on with the PipMenuView screen, why the dumpsys window cannot see it, but the surfaceflinger structure tree can be seen
insert image description here

Timing diagram:
insert image description here

When pip displays MenuView, the principle of app custom related buttons:

The app layer is controllable:

 if (mPictureInPictureParamsBuilder == null) {
    
    
                mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder();
            }
            RemoteAction action = new RemoteAction(Icon.createWithResource(this,R.mipmap.pasue),"hello","world", PendingIntent.getActivity(
                    this, 1,
                    new Intent(),
                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
            ));
            List<RemoteAction> list =new ArrayList<>();
            list.add(action);
            list.add(action);
            list.add(action);
            mPictureInPictureParamsBuilder.setActions(list);
            if (videoView != null) {
    
    

                // Calculate the aspect ratio of the PiP screen. 计算video的纵横比
                int mVideoWith = videoView.getWidth();
                int mVideoHeight = videoView.getHeight();
                Log.i("lsm","enterPiPMode mVideoWith = " + mVideoWith + " mVideoHeight = " + mVideoHeight);
                if (mVideoWith != 0 && mVideoHeight != 0) {
    
    
                    //设置param宽高比,根据宽高比例调整初始参数
                    Rational aspectRatio = new Rational(mVideoWith, mVideoHeight);
                    mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio);
                }

            }

The displayed effect is as follows:
insert image description here

You can see that there are 3 more icons. This is the interface part that Google left for the app to customize.
The most important solution here is the RemoteView solution, because PipMenuView belongs to the systemui process.
Here, several ActionViews are customized by each app process.

insert image description here

remoteview can also respond to related click events, but they are all through pendingintent.
For details, please refer to the Maxima course demo

Guess you like

Origin blog.csdn.net/learnframework/article/details/130855898