From 28eb5d5f7cdd1c9333ea9ac138dd0b2df900786d Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 7 Jul 2022 17:42:17 +0000 Subject: [PATCH] 2/ Add ShellInit callback mechanism - Keep track of callbacks in ShellInit and deprecate old path (to be removed in a follow up CL) Test: atest ShellInitImplTest Test: atest WMShellUnitTests Test: atest SystemUITests Bug: 238217847 Change-Id: Ic17d488ba8715641c01502bb501ad6876edf1455 --- .../com/android/wm/shell/ShellInitImpl.java | 59 +++++++- .../wm/shell/protolog/ShellProtoLogGroup.java | 6 +- .../android/wm/shell/ShellInitImplTest.java | 129 ++++++++++++++++++ 3 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 992f31562145c..5a94a0d6f6a5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -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; @@ -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(); @@ -64,6 +74,9 @@ public class ShellInitImpl { private final Optional mRecentTasks; private final InitImpl mImpl = new InitImpl(); + // An ordered list of init callbacks to be made once shell is first started + private final ArrayList> mInitCallbacks = new ArrayList<>(); + private boolean mHasInitialized; public ShellInitImpl( DisplayController displayController, @@ -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(); @@ -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 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 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); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index d04c34916256e..e31e0d674bec5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -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, @@ -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"); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java new file mode 100644 index 0000000000000..1effc97a0de36 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java @@ -0,0 +1,129 @@ +/* + * 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 mBubblesOptional; + @Mock private Optional mSplitScreenOptional; + @Mock private Optional mPipTouchHandlerOptional; + @Mock private FullscreenTaskListener mFullscreenTaskListener; + @Mock private Optional mUnfoldAnimationController; + @Mock private Optional mUnfoldTransitionHandler; + @Mock private Optional> mFreeformTaskListenerOptional; + @Mock private Optional 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 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 results = new ArrayList<>(); + mImpl.addInitCallback(() -> { + results.add(1); + }, new Object()); + mImpl.init(); + assertTrue(results.size() == 1); + mImpl.init(); + assertTrue(results.size() == 1); + } +}