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
This commit is contained in:
Winson Chung
2022-07-07 17:42:17 +00:00
parent ffce2a9e9f
commit 28eb5d5f7c
3 changed files with 189 additions and 5 deletions

View File

@@ -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<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,
@@ -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 <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);
}

View File

@@ -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");

View File

@@ -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<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);
}
}