Loading libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +56 −3 Original line number Diff line number Diff line Loading @@ -17,7 +17,15 @@ package com.android.wm.shell; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT; import android.os.Build; import android.os.SystemClock; import android.util.Pair; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; Loading @@ -37,10 +45,12 @@ import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.ArrayList; import java.util.Optional; /** * The entry point implementation into the shell for initializing shell internal state. * The entry point implementation into the shell for initializing shell internal state. Classes * which need to setup on start should inject an instance of this class and add an init callback. */ public class ShellInitImpl { private static final String TAG = ShellInitImpl.class.getSimpleName(); Loading @@ -64,6 +74,9 @@ public class ShellInitImpl { private final Optional<RecentTasksController> mRecentTasks; private final InitImpl mImpl = new InitImpl(); // An ordered list of init callbacks to be made once shell is first started private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>(); private boolean mHasInitialized; public ShellInitImpl( DisplayController displayController, Loading Loading @@ -106,7 +119,7 @@ public class ShellInitImpl { return mImpl; } private void init() { private void legacyInit() { // Start listening for display and insets changes mDisplayController.initialize(); mDisplayInsetsController.initialize(); Loading Loading @@ -153,12 +166,52 @@ public class ShellInitImpl { mKidsModeTaskOrganizer.initialize(mStartingWindow); } /** * Adds a callback to the ordered list of callbacks be made when Shell is first started. This * can be used in class constructors when dagger is used to ensure that the initialization order * matches the dependency order. */ public <T extends Object> void addInitCallback(Runnable r, T instance) { if (mHasInitialized) { if (Build.isDebuggable()) { // All callbacks must be added prior to the Shell being initialized throw new IllegalArgumentException("Can not add callback after init"); } return; } final String className = instance.getClass().getSimpleName(); mInitCallbacks.add(new Pair<>(className, r)); ProtoLog.v(WM_SHELL_INIT, "Adding init callback for %s", className); } /** * Calls all the init callbacks when the Shell is first starting. */ @VisibleForTesting public void init() { ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size()); // Init in order of registration for (int i = 0; i < mInitCallbacks.size(); i++) { final Pair<String, Runnable> info = mInitCallbacks.get(i); final long t1 = SystemClock.uptimeMillis(); info.second.run(); final long t2 = SystemClock.uptimeMillis(); ProtoLog.v(WM_SHELL_INIT, "\t%s took %dms", info.first, (t2 - t1)); } mInitCallbacks.clear(); // TODO: To be removed legacyInit(); mHasInitialized = true; } @ExternalThread private class InitImpl implements ShellInit { @Override public void init() { try { mMainExecutor.executeBlocking(() -> ShellInitImpl.this.init()); mMainExecutor.executeBlocking(ShellInitImpl.this::init); } catch (InterruptedException e) { throw new RuntimeException("Failed to initialize the Shell in 2s", e); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +4 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ import com.android.internal.protolog.common.IProtoLogGroup; public enum ShellProtoLogGroup implements IProtoLogGroup { // NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict // with those in the framework ProtoLogGroup WM_SHELL_INIT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Loading @@ -38,8 +40,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { "ShellBackPreview"), WM_SHELL_RECENT_TASKS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class ShellInitImplTest extends ShellTestCase { @Mock private DisplayController mDisplayController; @Mock private DisplayImeController mDisplayImeController; @Mock private DisplayInsetsController mDisplayInsetsController; @Mock private DragAndDropController mDragAndDropController; @Mock private ShellTaskOrganizer mShellTaskOrganizer; @Mock private KidsModeTaskOrganizer mKidsModeTaskOrganizer; @Mock private Optional<BubbleController> mBubblesOptional; @Mock private Optional<SplitScreenController> mSplitScreenOptional; @Mock private Optional<PipTouchHandler> mPipTouchHandlerOptional; @Mock private FullscreenTaskListener mFullscreenTaskListener; @Mock private Optional<UnfoldAnimationController> mUnfoldAnimationController; @Mock private Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler; @Mock private Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional; @Mock private Optional<RecentTasksController> mRecentTasks; @Mock private Transitions mTransitions; @Mock private StartingWindowController mStartingWindow; @Mock private ShellExecutor mMainExecutor; private ShellInitImpl mImpl; @Before public void setUp() { MockitoAnnotations.initMocks(this); mImpl = new ShellInitImpl(mDisplayController, mDisplayImeController, mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer, mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional, mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController, mUnfoldTransitionHandler, mFreeformTaskListenerOptional, mRecentTasks, mTransitions, mStartingWindow, mMainExecutor); } @Test public void testAddInitCallbacks_expectCalledInOrder() { ArrayList<Integer> results = new ArrayList<>(); mImpl.addInitCallback(() -> { results.add(1); }, new Object()); mImpl.addInitCallback(() -> { results.add(2); }, new Object()); mImpl.addInitCallback(() -> { results.add(3); }, new Object()); mImpl.init(); assertTrue(results.get(0) == 1); assertTrue(results.get(1) == 2); assertTrue(results.get(2) == 3); } @Test public void testNoInitCallbacksAfterInit_expectException() { mImpl.init(); try { mImpl.addInitCallback(() -> {}, new Object()); fail("Expected exception when adding callback after init"); } catch (IllegalArgumentException e) { // Expected } } @Test public void testDoubleInit_expectNoOp() { ArrayList<Integer> results = new ArrayList<>(); mImpl.addInitCallback(() -> { results.add(1); }, new Object()); mImpl.init(); assertTrue(results.size() == 1); mImpl.init(); assertTrue(results.size() == 1); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +56 −3 Original line number Diff line number Diff line Loading @@ -17,7 +17,15 @@ package com.android.wm.shell; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT; import android.os.Build; import android.os.SystemClock; import android.util.Pair; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; Loading @@ -37,10 +45,12 @@ import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.ArrayList; import java.util.Optional; /** * The entry point implementation into the shell for initializing shell internal state. * The entry point implementation into the shell for initializing shell internal state. Classes * which need to setup on start should inject an instance of this class and add an init callback. */ public class ShellInitImpl { private static final String TAG = ShellInitImpl.class.getSimpleName(); Loading @@ -64,6 +74,9 @@ public class ShellInitImpl { private final Optional<RecentTasksController> mRecentTasks; private final InitImpl mImpl = new InitImpl(); // An ordered list of init callbacks to be made once shell is first started private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>(); private boolean mHasInitialized; public ShellInitImpl( DisplayController displayController, Loading Loading @@ -106,7 +119,7 @@ public class ShellInitImpl { return mImpl; } private void init() { private void legacyInit() { // Start listening for display and insets changes mDisplayController.initialize(); mDisplayInsetsController.initialize(); Loading Loading @@ -153,12 +166,52 @@ public class ShellInitImpl { mKidsModeTaskOrganizer.initialize(mStartingWindow); } /** * Adds a callback to the ordered list of callbacks be made when Shell is first started. This * can be used in class constructors when dagger is used to ensure that the initialization order * matches the dependency order. */ public <T extends Object> void addInitCallback(Runnable r, T instance) { if (mHasInitialized) { if (Build.isDebuggable()) { // All callbacks must be added prior to the Shell being initialized throw new IllegalArgumentException("Can not add callback after init"); } return; } final String className = instance.getClass().getSimpleName(); mInitCallbacks.add(new Pair<>(className, r)); ProtoLog.v(WM_SHELL_INIT, "Adding init callback for %s", className); } /** * Calls all the init callbacks when the Shell is first starting. */ @VisibleForTesting public void init() { ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size()); // Init in order of registration for (int i = 0; i < mInitCallbacks.size(); i++) { final Pair<String, Runnable> info = mInitCallbacks.get(i); final long t1 = SystemClock.uptimeMillis(); info.second.run(); final long t2 = SystemClock.uptimeMillis(); ProtoLog.v(WM_SHELL_INIT, "\t%s took %dms", info.first, (t2 - t1)); } mInitCallbacks.clear(); // TODO: To be removed legacyInit(); mHasInitialized = true; } @ExternalThread private class InitImpl implements ShellInit { @Override public void init() { try { mMainExecutor.executeBlocking(() -> ShellInitImpl.this.init()); mMainExecutor.executeBlocking(ShellInitImpl.this::init); } catch (InterruptedException e) { throw new RuntimeException("Failed to initialize the Shell in 2s", e); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +4 −2 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ import com.android.internal.protolog.common.IProtoLogGroup; public enum ShellProtoLogGroup implements IProtoLogGroup { // NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict // with those in the framework ProtoLogGroup WM_SHELL_INIT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Loading @@ -38,8 +40,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { "ShellBackPreview"), WM_SHELL_RECENT_TASKS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class ShellInitImplTest extends ShellTestCase { @Mock private DisplayController mDisplayController; @Mock private DisplayImeController mDisplayImeController; @Mock private DisplayInsetsController mDisplayInsetsController; @Mock private DragAndDropController mDragAndDropController; @Mock private ShellTaskOrganizer mShellTaskOrganizer; @Mock private KidsModeTaskOrganizer mKidsModeTaskOrganizer; @Mock private Optional<BubbleController> mBubblesOptional; @Mock private Optional<SplitScreenController> mSplitScreenOptional; @Mock private Optional<PipTouchHandler> mPipTouchHandlerOptional; @Mock private FullscreenTaskListener mFullscreenTaskListener; @Mock private Optional<UnfoldAnimationController> mUnfoldAnimationController; @Mock private Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler; @Mock private Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional; @Mock private Optional<RecentTasksController> mRecentTasks; @Mock private Transitions mTransitions; @Mock private StartingWindowController mStartingWindow; @Mock private ShellExecutor mMainExecutor; private ShellInitImpl mImpl; @Before public void setUp() { MockitoAnnotations.initMocks(this); mImpl = new ShellInitImpl(mDisplayController, mDisplayImeController, mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer, mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional, mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController, mUnfoldTransitionHandler, mFreeformTaskListenerOptional, mRecentTasks, mTransitions, mStartingWindow, mMainExecutor); } @Test public void testAddInitCallbacks_expectCalledInOrder() { ArrayList<Integer> results = new ArrayList<>(); mImpl.addInitCallback(() -> { results.add(1); }, new Object()); mImpl.addInitCallback(() -> { results.add(2); }, new Object()); mImpl.addInitCallback(() -> { results.add(3); }, new Object()); mImpl.init(); assertTrue(results.get(0) == 1); assertTrue(results.get(1) == 2); assertTrue(results.get(2) == 3); } @Test public void testNoInitCallbacksAfterInit_expectException() { mImpl.init(); try { mImpl.addInitCallback(() -> {}, new Object()); fail("Expected exception when adding callback after init"); } catch (IllegalArgumentException e) { // Expected } } @Test public void testDoubleInit_expectNoOp() { ArrayList<Integer> results = new ArrayList<>(); mImpl.addInitCallback(() -> { results.add(1); }, new Object()); mImpl.init(); assertTrue(results.size() == 1); mImpl.init(); assertTrue(results.size() == 1); } }