Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c8773d07 authored by Jerry Chang's avatar Jerry Chang
Browse files

Respect feature flag when providing Pip controller

Makes sysui dagger provides optional PIP controller with PIP feature
flag. Adds test in PipControllerTest to make sure it won't instantiate
controller if the feature is not supported.

Bug: 170188471
Test: atest SystemUITests
Test: atest WMShellUnitTests
Change-Id: I681bb5ac2972d6426c270099605d44fc82dc9f78
parent c48de401
Loading
Loading
Loading
Loading
+2 −8
Original line number Original line Diff line number Diff line
@@ -67,7 +67,6 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.pip.phone.PipMenuActivityController;
import com.android.wm.shell.pip.phone.PipMenuActivityController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipUpdateThread;
import com.android.wm.shell.pip.phone.PipUpdateThread;
import com.android.wm.shell.pip.phone.PipUtils;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen;


import java.io.PrintWriter;
import java.io.PrintWriter;
@@ -281,14 +280,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
        mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
        mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
        mSplitScreenOptional = splitScreenOptional;
        mSplitScreenOptional = splitScreenOptional;
        mTaskOrganizer = shellTaskOrganizer;
        mTaskOrganizer = shellTaskOrganizer;

        if (!PipUtils.hasSystemFeature(context)) {
            Log.w(TAG, "Device not support PIP feature");
        } else {
        mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_PIP);
        mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_PIP);
        displayController.addDisplayWindowListener(this);
        displayController.addDisplayWindowListener(this);
    }
    }
    }


    public Handler getUpdateHandler() {
    public Handler getUpdateHandler() {
        return mUpdateHandler;
        return mUpdateHandler;
+28 −27
Original line number Original line Diff line number Diff line
@@ -17,11 +17,10 @@
package com.android.wm.shell.pip.phone;
package com.android.wm.shell.pip.phone;


import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;


import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;


import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.app.PictureInPictureParams;
import android.app.RemoteAction;
import android.app.RemoteAction;
@@ -34,11 +33,14 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager;
import android.util.Log;
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.DisplayInfo;
import android.view.IPinnedStackController;
import android.view.IPinnedStackController;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction;


import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayController;
@@ -196,7 +198,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
        }
        }
    }
    }


    public PipController(Context context,
    protected PipController(Context context,
            DisplayController displayController,
            DisplayController displayController,
            PipAppOpsListener pipAppOpsListener,
            PipAppOpsListener pipAppOpsListener,
            PipBoundsHandler pipBoundsHandler,
            PipBoundsHandler pipBoundsHandler,
@@ -207,34 +209,13 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
            PipTouchHandler pipTouchHandler,
            PipTouchHandler pipTouchHandler,
            WindowManagerShellWrapper windowManagerShellWrapper
            WindowManagerShellWrapper windowManagerShellWrapper
    ) {
    ) {
        mContext = context;

        if (PipUtils.hasSystemFeature(mContext)) {
            initController(context, displayController, pipAppOpsListener, pipBoundsHandler,
                    pipBoundsState, pipMediaController, pipMenuActivityController, pipTaskOrganizer,
                    pipTouchHandler, windowManagerShellWrapper);
        } else {
            Log.w(TAG, "Device not support PIP feature");
        }
    }

    private void initController(Context context,
            DisplayController displayController,
            PipAppOpsListener pipAppOpsListener,
            PipBoundsHandler pipBoundsHandler,
            @NonNull PipBoundsState pipBoundsState,
            PipMediaController pipMediaController,
            PipMenuActivityController pipMenuActivityController,
            PipTaskOrganizer pipTaskOrganizer,
            PipTouchHandler pipTouchHandler,
            WindowManagerShellWrapper windowManagerShellWrapper) {

        // Ensure that we are the primary user's SystemUI.
        // Ensure that we are the primary user's SystemUI.
        final int processUser = UserManager.get(context).getUserHandle();
        final int processUser = UserManager.get(context).getUserHandle();
        if (processUser != UserHandle.USER_SYSTEM) {
        if (processUser != UserHandle.USER_SYSTEM) {
            throw new IllegalStateException("Non-primary Pip component not currently supported.");
            throw new IllegalStateException("Non-primary Pip component not currently supported.");
        }
        }


        mContext = context;
        mWindowManagerShellWrapper = windowManagerShellWrapper;
        mWindowManagerShellWrapper = windowManagerShellWrapper;
        mDisplayController = displayController;
        mDisplayController = displayController;
        mPipBoundsHandler = pipBoundsHandler;
        mPipBoundsHandler = pipBoundsHandler;
@@ -258,7 +239,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
            mWindowManagerShellWrapper.addPinnedStackListener(
            mWindowManagerShellWrapper.addPinnedStackListener(
                    new PipControllerPinnedStackListener());
                    new PipControllerPinnedStackListener());
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to register pinned stack listener", e);
            Slog.e(TAG, "Failed to register pinned stack listener", e);
        }
        }
    }
    }


@@ -465,4 +446,24 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
        mPipTaskOrganizer.dump(pw, innerPrefix);
        mPipTaskOrganizer.dump(pw, innerPrefix);
        mPipBoundsState.dump(pw, innerPrefix);
        mPipBoundsState.dump(pw, innerPrefix);
    }
    }

    /**
     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
     */
    @Nullable
    public static PipController create(Context context, DisplayController displayController,
            PipAppOpsListener pipAppOpsListener, PipBoundsHandler pipBoundsHandler,
            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
            PipMenuActivityController pipMenuActivityController,
            PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler,
            WindowManagerShellWrapper windowManagerShellWrapper) {
        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
            Slog.w(TAG, "Device doesn't support Pip feature");
            return null;
        }

        return new PipController(context, displayController, pipAppOpsListener, pipBoundsHandler,
                pipBoundsState, pipMediaController, pipMenuActivityController,
                pipTaskOrganizer, pipTouchHandler, windowManagerShellWrapper);
    }
}
}
+0 −12
Original line number Original line Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.wm.shell.pip.phone;


import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;


import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.ActivityTaskManager.RootTaskInfo;
@@ -30,7 +29,6 @@ import android.util.Log;
import android.util.Pair;
import android.util.Pair;


public class PipUtils {
public class PipUtils {

    private static final String TAG = "PipUtils";
    private static final String TAG = "PipUtils";


    /**
    /**
@@ -58,14 +56,4 @@ public class PipUtils {
        }
        }
        return new Pair<>(null, 0);
        return new Pair<>(null, 0);
    }
    }

    /**
     * The util to check if device has PIP feature
     *
     * @param context application context
     * @return true if device has PIP feature, false otherwise.
     */
    public static boolean hasSystemFeature(Context context) {
        return context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
    }
}
}
+28 −28
Original line number Original line Diff line number Diff line
@@ -18,17 +18,18 @@ package com.android.wm.shell.pip.phone;


import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;


import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;


import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper;


import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -37,10 +38,6 @@ import com.android.wm.shell.pip.PipBoundsHandler;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTestCase;
import com.android.wm.shell.pip.PipTestCase;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMediaController;
import com.android.wm.shell.pip.phone.PipTouchHandler;


import org.junit.Before;
import org.junit.Before;
import org.junit.Test;
import org.junit.Test;
@@ -55,49 +52,52 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@TestableLooper.RunWithLooper
public class PipControllerTest extends PipTestCase {
public class PipControllerTest extends PipTestCase {
    private com.android.wm.shell.pip.phone.PipController mPipController;
    private PipController mPipController;
    private TestableContext mSpiedContext;


    @Mock private DisplayController mMockdDisplayController;
    @Mock private DisplayController mMockDisplayController;
    @Mock private PackageManager mPackageManager;
    @Mock private PipMenuActivityController mMockPipMenuActivityController;
    @Mock private com.android.wm.shell.pip.phone.PipMenuActivityController
            mMockPipMenuActivityController;
    @Mock private PipAppOpsListener mMockPipAppOpsListener;
    @Mock private PipAppOpsListener mMockPipAppOpsListener;
    @Mock private PipBoundsHandler mMockPipBoundsHandler;
    @Mock private PipBoundsHandler mMockPipBoundsHandler;
    @Mock private PipMediaController mMockPipMediaController;
    @Mock private PipMediaController mMockPipMediaController;
    @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
    @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
    @Mock private PipTouchHandler mMockPipTouchHandler;
    @Mock private PipTouchHandler mMockPipTouchHandler;
    @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
    @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
    private PipBoundsState mPipBoundsState;
    @Mock private PipBoundsState mMockPipBoundsState;


    @Before
    @Before
    public void setUp() throws RemoteException {
    public void setUp() throws RemoteException {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
        mPipBoundsState = new PipBoundsState();
        mPipController = new PipController(mContext, mMockDisplayController,

                mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipBoundsState,
        mSpiedContext = spy(mContext);

        when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
        when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);

        mPipController = new PipController(mSpiedContext, mMockdDisplayController,
                mMockPipAppOpsListener, mMockPipBoundsHandler, mPipBoundsState,
                mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
                mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
                mMockPipTouchHandler, mMockWindowManagerShellWrapper);
                mMockPipTouchHandler, mMockWindowManagerShellWrapper);
    }
    }


    @Test
    @Test
    public void testNonPipDevice_shouldNotRegisterPipTransitionCallback() {
    public void instantiatePipController_registersPipTransitionCallback() {
        verify(mMockPipTaskOrganizer, never()).registerPipTransitionCallback(any());
        verify(mMockPipTaskOrganizer).registerPipTransitionCallback(any());
    }

    @Test
    public void instantiatePipController_addsDisplayChangingController() {
        verify(mMockDisplayController).addDisplayChangingController(any());
    }
    }


    @Test
    @Test
    public void testNonPipDevice_shouldNotAddDisplayChangingController() {
    public void instantiatePipController_addsDisplayWindowListener() {
        verify(mMockdDisplayController, never()).addDisplayChangingController(any());
        verify(mMockDisplayController).addDisplayWindowListener(any());
    }
    }


    @Test
    @Test
    public void testNonPipDevice_shouldNotAddDisplayWindowListener() {
    public void createPip_notSupported_returnsNull() {
        verify(mMockdDisplayController, never()).addDisplayWindowListener(any());
        Context spyContext = spy(mContext);
        PackageManager mockPackageManager = mock(PackageManager.class);
        when(mockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
        when(spyContext.getPackageManager()).thenReturn(mockPackageManager);

        assertNull(PipController.create(spyContext, mMockDisplayController,
                mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipBoundsState,
                mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
                mMockPipTouchHandler, mMockWindowManagerShellWrapper));
    }
    }
}
}
+7 −23
Original line number Original line Diff line number Diff line
@@ -16,20 +16,13 @@


package com.android.wm.shell.pip.phone;
package com.android.wm.shell.pip.phone;


import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper;


import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -58,39 +51,30 @@ import java.util.Optional;
@TestableLooper.RunWithLooper
@TestableLooper.RunWithLooper
public class PipTaskOrganizerTest extends PipTestCase {
public class PipTaskOrganizerTest extends PipTestCase {
    private PipTaskOrganizer mSpiedPipTaskOrganizer;
    private PipTaskOrganizer mSpiedPipTaskOrganizer;
    private TestableContext mSpiedContext;


    @Mock private DisplayController mMockdDisplayController;
    @Mock private DisplayController mMockdDisplayController;
    @Mock private PackageManager mPackageManager;
    @Mock private PipBoundsHandler mMockPipBoundsHandler;
    @Mock private PipBoundsHandler mMockPipBoundsHandler;
    @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
    @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
    @Mock private PipUiEventLogger mMockPipUiEventLogger;
    @Mock private PipUiEventLogger mMockPipUiEventLogger;
    @Mock private Optional<SplitScreen> mMockOptionalSplitScreen;
    @Mock private Optional<SplitScreen> mMockOptionalSplitScreen;
    @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
    @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
    private PipBoundsState mPipBoundsState;
    @Mock private PipBoundsState mMockPipBoundsState;


    @Before
    @Before
    public void setUp() throws RemoteException {
    public void setUp() throws RemoteException {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
        mPipBoundsState = new PipBoundsState();
        mSpiedPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockPipBoundsState,

        mSpiedContext = spy(mContext);

        when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
        when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);

        mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mSpiedContext, mPipBoundsState,
                mMockPipBoundsHandler, mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen,
                mMockPipBoundsHandler, mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen,
                mMockdDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer));
                mMockdDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer);
    }
    }


    @Test
    @Test
    public void testNonPipDevice_shellTaskOrganizer_shouldNotAddListener() {
    public void instantiatePipTaskOrganizer_addsTaskListener() {
        verify(mMockShellTaskOrganizer, never()).addListener(any(), anyInt());
        verify(mMockShellTaskOrganizer).addListener(any(), anyInt());
    }
    }


    @Test
    @Test
    public void testNonPipDevice_displayController_shouldNotAddDisplayWindowListener() {
    public void instantiatePipTaskOrganizer_addsDisplayWindowListener() {
        verify(mMockdDisplayController, never()).addDisplayWindowListener(any());
        verify(mMockdDisplayController).addDisplayWindowListener(any());
    }
    }
}
}
Loading