Add TaskViewFactory.

- This CL also changes TV.setListener to have a separate Executor.

Bug: 165794075
Test: atest TaskViewTest BubblesTest NewNotifPipelineBubblesTest
Change-Id: Ib0324ec06d088694556f8cef6cee726d9789ce2c
This commit is contained in:
Yuncheol Heo
2020-12-16 16:46:39 -08:00
parent cde9548a2c
commit f3929238b0
7 changed files with 188 additions and 74 deletions

View File

@@ -73,6 +73,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private final CloseGuard mGuard = new CloseGuard();
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
@@ -81,7 +82,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private boolean mSurfaceCreated;
private boolean mIsInitialized;
private Listener mListener;
private Executor mExecutor;
private Executor mListenerExecutor;
private final Rect mTmpRect = new Rect();
private final Rect mTmpRootRect = new Rect();
@@ -91,7 +92,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
super(context, null, 0, 0, true /* disableBackgroundLayer */);
mTaskOrganizer = organizer;
mExecutor = organizer.getExecutor();
mShellExecutor = organizer.getExecutor();
setUseAlpha();
getHolder().addCallback(this);
mGuard.open("release");
@@ -100,12 +101,13 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
/**
* Only one listener may be set on the view, throws an exception otherwise.
*/
public void setListener(Listener listener) {
public void setListener(@NonNull Executor executor, Listener listener) {
if (mListener != null) {
throw new IllegalStateException(
"Trying to set a listener when one has already been set");
}
mListener = listener;
mListenerExecutor = executor;
}
/**
@@ -150,7 +152,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private void prepareActivityOptions(ActivityOptions options) {
final Binder launchCookie = new Binder();
mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
mShellExecutor.execute(() -> {
mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
});
options.setLaunchCookie(launchCookie);
options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
options.setTaskAlwaysOnTop(true);
@@ -197,11 +201,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private void performRelease() {
getHolder().removeCallback(this);
mTaskOrganizer.removeListener(this);
resetTaskInfo();
mShellExecutor.execute(() -> {
mTaskOrganizer.removeListener(this);
resetTaskInfo();
});
mGuard.close();
if (mListener != null && mIsInitialized) {
mListener.onReleased();
mListenerExecutor.execute(() -> {
mListener.onReleased();
});
mIsInitialized = false;
}
}
@@ -218,75 +226,71 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
mTaskOrganizer.applyTransaction(wct);
// TODO(b/151449487): Only call callback once we enable synchronization
if (mListener != null) {
mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
mListenerExecutor.execute(() -> {
mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
});
}
}
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash) {
if (mExecutor == null) return;
mExecutor.execute(() -> {
mTaskInfo = taskInfo;
mTaskToken = taskInfo.token;
mTaskLeash = leash;
mTaskInfo = taskInfo;
mTaskToken = taskInfo.token;
mTaskLeash = leash;
if (mSurfaceCreated) {
// Surface is ready, so just reparent the task to this surface control
mTransaction.reparent(mTaskLeash, getSurfaceControl())
.show(mTaskLeash)
.apply();
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
updateTaskVisibility();
}
mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
// TODO: Synchronize show with the resize
onLocationChanged();
setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
if (mSurfaceCreated) {
// Surface is ready, so just reparent the task to this surface control
mTransaction.reparent(mTaskLeash, getSurfaceControl())
.show(mTaskLeash)
.apply();
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
updateTaskVisibility();
}
mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
// TODO: Synchronize show with the resize
onLocationChanged();
setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
if (mListener != null) {
if (mListener != null) {
mListenerExecutor.execute(() -> {
mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
}
});
});
}
}
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
if (mExecutor == null) return;
mExecutor.execute(() -> {
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
if (mListener != null) {
if (mListener != null) {
mListenerExecutor.execute(() -> {
mListener.onTaskRemovalStarted(taskInfo.taskId);
}
mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
});
}
mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
// Unparent the task when this surface is destroyed
mTransaction.reparent(mTaskLeash, null).apply();
resetTaskInfo();
});
// Unparent the task when this surface is destroyed
mTransaction.reparent(mTaskLeash, null).apply();
resetTaskInfo();
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (mExecutor == null) return;
mExecutor.execute(() -> {
mTaskInfo.taskDescription = taskInfo.taskDescription;
setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
});
mTaskInfo.taskDescription = taskInfo.taskDescription;
setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
}
@Override
public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
if (mExecutor == null) return;
mExecutor.execute(() -> {
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
if (mListener != null) {
if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
if (mListener != null) {
mListenerExecutor.execute(() -> {
mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
}
});
});
}
}
@Override
@@ -306,17 +310,21 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
mSurfaceCreated = true;
if (mListener != null && !mIsInitialized) {
mIsInitialized = true;
mListener.onInitialized();
mListenerExecutor.execute(() -> {
mListener.onInitialized();
});
}
if (mTaskToken == null) {
// Nothing to update, task is not yet available
return;
}
// Reparent the task when this surface is created
mTransaction.reparent(mTaskLeash, getSurfaceControl())
.show(mTaskLeash)
.apply();
updateTaskVisibility();
mShellExecutor.execute(() -> {
if (mTaskToken == null) {
// Nothing to update, task is not yet available
return;
}
// Reparent the task when this surface is created
mTransaction.reparent(mTaskLeash, getSurfaceControl())
.show(mTaskLeash)
.apply();
updateTaskVisibility();
});
}
@Override
@@ -330,14 +338,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mSurfaceCreated = false;
if (mTaskToken == null) {
// Nothing to update, task is not yet available
return;
}
mShellExecutor.execute(() -> {
if (mTaskToken == null) {
// Nothing to update, task is not yet available
return;
}
// Unparent the task when this surface is destroyed
mTransaction.reparent(mTaskLeash, null).apply();
updateTaskVisibility();
// Unparent the task when this surface is destroyed
mTransaction.reparent(mTaskLeash, null).apply();
updateTaskVisibility();
});
}
@Override

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2020 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 android.annotation.UiContext;
import android.content.Context;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/** Interface to create TaskView. */
@ExternalThread
public interface TaskViewFactory {
/** Creates an {@link TaskView} */
void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate);
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 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 android.annotation.UiContext;
import android.content.Context;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/** Factory controller which can create {@link TaskView} */
public class TaskViewFactoryController {
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellExecutor mShellExecutor;
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
ShellExecutor shellExecutor) {
mTaskOrganizer = taskOrganizer;
mShellExecutor = shellExecutor;
}
/** Creates an {@link TaskView} */
@ShellMainThread
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
TaskView taskView = new TaskView(context, mTaskOrganizer);
executor.execute(() -> {
onCreate.accept(taskView);
});
}
public TaskViewFactory getTaskViewFactory() {
return new TaskViewFactoryImpl();
}
private class TaskViewFactoryImpl implements TaskViewFactory {
@ExternalThread
public void create(@UiContext Context context,
Executor executor, Consumer<TaskView> onCreate) {
mShellExecutor.execute(() -> {
TaskViewFactoryController.this.create(context, executor, onCreate);
});
}
}
}

View File

@@ -314,7 +314,7 @@ public class BubbleExpandedView extends LinearLayout {
mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
mExpandedViewContainer.addView(mTaskView);
bringChildToFront(mTaskView);
mTaskView.setListener(mTaskViewListener);
mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
mPositioner = mController.getPositioner();
}

View File

@@ -99,7 +99,7 @@ public class TaskViewTest extends ShellTestCase {
when(mOrganizer.getExecutor()).thenReturn(mExecutor);
mTaskView = new TaskView(mContext, mOrganizer);
mTaskView.setListener(mViewListener);
mTaskView.setListener(mExecutor, mViewListener);
}
@After
@@ -112,9 +112,9 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testSetPendingListener_throwsException() {
TaskView taskView = new TaskView(mContext, mOrganizer);
taskView.setListener(mViewListener);
taskView.setListener(mExecutor, mViewListener);
try {
taskView.setListener(mViewListener);
taskView.setListener(mExecutor, mViewListener);
} catch (IllegalStateException e) {
// pass
return;

View File

@@ -89,6 +89,8 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
@@ -204,6 +206,8 @@ public class BubblesTest extends SysuiTestCase {
private WindowManagerShellWrapper mWindowManagerShellWrapper;
@Mock
private BubbleLogger mBubbleLogger;
@Mock
private ShellTaskOrganizer mShellTaskOrganizer;
private TestableBubblePositioner mPositioner;
@@ -269,6 +273,7 @@ public class BubblesTest extends SysuiTestCase {
);
when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock()));
mBubbleController = new TestableBubbleController(
mContext,
mBubbleData,
@@ -279,7 +284,7 @@ public class BubblesTest extends SysuiTestCase {
mWindowManagerShellWrapper,
mLauncherApps,
mBubbleLogger,
mock(ShellTaskOrganizer.class),
mShellTaskOrganizer,
mPositioner,
mock(ShellExecutor.class));
mBubbleController.setExpandListener(mBubbleExpandListener);

View File

@@ -83,6 +83,8 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.BubbleData;
@@ -186,6 +188,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
private WindowManagerShellWrapper mWindowManagerShellWrapper;
@Mock
private BubbleLogger mBubbleLogger;
@Mock
private ShellTaskOrganizer mShellTaskOrganizer;
private TestableBubblePositioner mPositioner;
@@ -237,6 +241,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mock(Handler.class)
);
when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock()));
mBubbleController = new TestableBubbleController(
mContext,
mBubbleData,
@@ -247,7 +252,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mWindowManagerShellWrapper,
mLauncherApps,
mBubbleLogger,
mock(ShellTaskOrganizer.class),
mShellTaskOrganizer,
mPositioner,
mock(ShellExecutor.class));
mBubbleController.setExpandListener(mBubbleExpandListener);