Loading core/java/android/app/ActivityManager.java +56 −0 Original line number Diff line number Diff line Loading @@ -6312,6 +6312,38 @@ public class ActivityManager { * See {@link android.app.ActivityManager#getAppTasks()} */ public static class AppTask { /** * The windowing layer is not specified. The system will use a * {@link #WINDOWING_LAYER_NORMAL_APP} layer. * @hide */ public static final int WINDOWING_LAYER_UNDEFINED = 0; /** * The windowing layer for normal application windows. * @hide */ public static final int WINDOWING_LAYER_NORMAL_APP = 1; /** * The windowing layer for pinned windows, these windows are typically displayed above * normal application windows. * @hide */ public static final int WINDOWING_LAYER_PINNED = 2; /** * Defines the windowing layer for a task, which can affect its Z-ordering. * @hide */ @IntDef(prefix = { "WINDOWING_LAYER_" }, value = { WINDOWING_LAYER_UNDEFINED, WINDOWING_LAYER_NORMAL_APP, WINDOWING_LAYER_PINNED, }) @Retention(RetentionPolicy.SOURCE) public @interface WindowingLayer { } private IAppTask mAppTaskImpl; /** @hide */ Loading Loading @@ -6458,6 +6490,30 @@ public class ActivityManager { throw e.rethrowFromSystemServer(); } } /** * Requests the windowing layer for this task. This can be used to affect the Z-ordering * of the activity's window relative to other windows. * * <p> * The task will be moved to the requested layer if possible. * * @param layer the {@link WindowingLayer} to move task to. * @param executor an Executor used to invoke the callback * @param callback a callback to receive the result of the request * @hide */ // TODO(b/442807136): Complete javadoc, add all requirements and detals needed public void requestWindowingLayer( @WindowingLayer int layer, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback) { Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(callback, "callback cannot be null"); TaskWindowingLayerRequestHandler.requestWindowingLayer( layer, executor, callback, mAppTaskImpl ); } } /** Loading core/java/android/app/IAppTask.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -33,4 +33,5 @@ interface IAppTask { int startActivity(IBinder whoThread, String callingPackage, String callingFeatureId, in Intent intent, String resolvedType, in Bundle options); void setExcludeFromRecents(boolean exclude); void requestWindowingLayer(in int layer, in IRemoteCallback outcomeCallback); } core/java/android/app/TaskWindowingLayerRequestHandler.java 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.app; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.OutcomeReceiver; import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; import java.util.concurrent.Executor; /** * Class to encapsulate handling {@link ActivityManager.AppTask#requestWindowingLayer} requests and * related utilities. * @hide */ public class TaskWindowingLayerRequestHandler { /** * The key used for specifying the final result of a windowing layer request in the * {@link android.os.Bundle} returned by the server. */ public static final String REMOTE_CALLBACK_RESULT_KEY = "result"; /** * The request had been approved. * * <p> * The task layer has been changed accordingly to the request. */ public static final int RESULT_APPROVED = 0; /** * The request has been rejected because the windowing system was in an inappropriate state to * handle the request. */ public static final int RESULT_FAILED_BAD_STATE = 1; /** * Requests the windowing layer via {@link IAppTask}. */ @VisibleForTesting(visibility = PACKAGE) public static void requestWindowingLayer( @ActivityManager.AppTask.WindowingLayer int layer, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback, @NonNull IAppTask appTaskImpl) { try { appTaskImpl.requestWindowingLayer(layer, createRemoteCallback(executor, callback)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private static IRemoteCallback createRemoteCallback( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback ) { return new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) { final int result = data.getInt(REMOTE_CALLBACK_RESULT_KEY); switch (result) { case RESULT_APPROVED: executor.execute(() -> callback.onResult(null)); break; case RESULT_FAILED_BAD_STATE: executor.execute(() -> callback.onError(new IllegalStateException( "The current system windowing state is not appropriate to fulfill" + " the request."))); break; default: executor.execute(() -> callback.onError(new IllegalStateException( "Unknown error, code=" + result))); break; } } }; } } core/tests/coretests/src/android/app/OWNERS +2 −0 Original line number Diff line number Diff line # Files related to windowing per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file TaskWindowingLayerRequestHandlerTest.java = file:/services/core/java/com/android/server/wm/OWNERS # Notification, DND, Status bar per-file *Notification* = file:/packages/SystemUI/OWNERS Loading core/tests/coretests/src/android/app/TaskWindowingLayerRequestHandlerTest.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.app; import static android.app.ActivityManager.AppTask.WINDOWING_LAYER_NORMAL_APP; import static android.app.ActivityManager.AppTask.WINDOWING_LAYER_PINNED; import static android.app.TaskWindowingLayerRequestHandler.REMOTE_CALLBACK_RESULT_KEY; import static android.app.TaskWindowingLayerRequestHandler.RESULT_APPROVED; import static android.app.TaskWindowingLayerRequestHandler.RESULT_FAILED_BAD_STATE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.concurrent.Executor; /** * Test class for {@link TaskWindowingLayerRequestHandler}. * * Build/Install/Run: * atest TaskWindowingLayerRequestHandlerTest */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) public class TaskWindowingLayerRequestHandlerTest { private static final Executor SAME_THREAD_EXECUTOR = Runnable::run; private IAppTask mAppTask; private OutcomeReceiver<Void, Exception> mCallback; private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private ArgumentCaptor<Exception> mExceptionCaptor; @Before public void setUp() { mAppTask = mock(IAppTask.class); mCallback = mock(OutcomeReceiver.class); mRemoteCallbackCaptor = ArgumentCaptor.forClass(IRemoteCallback.class); mExceptionCaptor = ArgumentCaptor.forClass(Exception.class); } @Test public void requestWindowingLayer_propagatesSuccessResult() throws RemoteException { final Bundle actualResult = resultWithCode(RESULT_APPROVED); TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_NORMAL_APP, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); verify(mAppTask).requestWindowingLayer(eq(WINDOWING_LAYER_NORMAL_APP), mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(actualResult); verifyCallbackReceivedSuccessResultOnce(); } @Test public void requestWindowingLayer_propagatesBadStateFailure() throws RemoteException { final Bundle actualResult = resultWithCode(RESULT_FAILED_BAD_STATE); TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_PINNED, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); verify(mAppTask).requestWindowingLayer(eq(WINDOWING_LAYER_PINNED), mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(actualResult); verifyCallbackReceivedErrorOnce(IllegalStateException.class, "The current system windowing state is not appropriate to fulfill the request."); } @Test public void requestWindowingLayer_whenInvalidResult_propagatesError() throws RemoteException { final Bundle actualResult = resultWithCode(-1); // invalid code TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_PINNED, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); verify(mAppTask).requestWindowingLayer(eq(WINDOWING_LAYER_PINNED), mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(actualResult); verifyCallbackReceivedErrorOnce(IllegalStateException.class, "Unknown error, code=-1"); } @Test(expected = RuntimeException.class) public void requestWindowingLayer_whenRemoteException_isRethrown() throws RemoteException { doThrow(new RemoteException("foo")).when(mAppTask) .requestWindowingLayer(anyInt(), any()); TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_PINNED, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); } private void verifyCallbackReceivedSuccessResultOnce() { verify(mCallback).onResult(any()); verify(mCallback, never()).onError(any()); } private <E extends Exception> void verifyCallbackReceivedErrorOnce(Class<E> expectedException, String expectedMessage) { verify(mCallback, never()).onResult(any()); verify(mCallback).onError(mExceptionCaptor.capture()); Exception captured = mExceptionCaptor.getValue(); assertTrue(expectedException.isInstance(captured)); assertEquals(captured.getMessage(), expectedMessage); } private static Bundle resultWithCode(int result) { Bundle resultData = new Bundle(); resultData.putInt(REMOTE_CALLBACK_RESULT_KEY, result); return resultData; } } Loading
core/java/android/app/ActivityManager.java +56 −0 Original line number Diff line number Diff line Loading @@ -6312,6 +6312,38 @@ public class ActivityManager { * See {@link android.app.ActivityManager#getAppTasks()} */ public static class AppTask { /** * The windowing layer is not specified. The system will use a * {@link #WINDOWING_LAYER_NORMAL_APP} layer. * @hide */ public static final int WINDOWING_LAYER_UNDEFINED = 0; /** * The windowing layer for normal application windows. * @hide */ public static final int WINDOWING_LAYER_NORMAL_APP = 1; /** * The windowing layer for pinned windows, these windows are typically displayed above * normal application windows. * @hide */ public static final int WINDOWING_LAYER_PINNED = 2; /** * Defines the windowing layer for a task, which can affect its Z-ordering. * @hide */ @IntDef(prefix = { "WINDOWING_LAYER_" }, value = { WINDOWING_LAYER_UNDEFINED, WINDOWING_LAYER_NORMAL_APP, WINDOWING_LAYER_PINNED, }) @Retention(RetentionPolicy.SOURCE) public @interface WindowingLayer { } private IAppTask mAppTaskImpl; /** @hide */ Loading Loading @@ -6458,6 +6490,30 @@ public class ActivityManager { throw e.rethrowFromSystemServer(); } } /** * Requests the windowing layer for this task. This can be used to affect the Z-ordering * of the activity's window relative to other windows. * * <p> * The task will be moved to the requested layer if possible. * * @param layer the {@link WindowingLayer} to move task to. * @param executor an Executor used to invoke the callback * @param callback a callback to receive the result of the request * @hide */ // TODO(b/442807136): Complete javadoc, add all requirements and detals needed public void requestWindowingLayer( @WindowingLayer int layer, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback) { Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(callback, "callback cannot be null"); TaskWindowingLayerRequestHandler.requestWindowingLayer( layer, executor, callback, mAppTaskImpl ); } } /** Loading
core/java/android/app/IAppTask.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -33,4 +33,5 @@ interface IAppTask { int startActivity(IBinder whoThread, String callingPackage, String callingFeatureId, in Intent intent, String resolvedType, in Bundle options); void setExcludeFromRecents(boolean exclude); void requestWindowingLayer(in int layer, in IRemoteCallback outcomeCallback); }
core/java/android/app/TaskWindowingLayerRequestHandler.java 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.app; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.OutcomeReceiver; import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; import java.util.concurrent.Executor; /** * Class to encapsulate handling {@link ActivityManager.AppTask#requestWindowingLayer} requests and * related utilities. * @hide */ public class TaskWindowingLayerRequestHandler { /** * The key used for specifying the final result of a windowing layer request in the * {@link android.os.Bundle} returned by the server. */ public static final String REMOTE_CALLBACK_RESULT_KEY = "result"; /** * The request had been approved. * * <p> * The task layer has been changed accordingly to the request. */ public static final int RESULT_APPROVED = 0; /** * The request has been rejected because the windowing system was in an inappropriate state to * handle the request. */ public static final int RESULT_FAILED_BAD_STATE = 1; /** * Requests the windowing layer via {@link IAppTask}. */ @VisibleForTesting(visibility = PACKAGE) public static void requestWindowingLayer( @ActivityManager.AppTask.WindowingLayer int layer, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback, @NonNull IAppTask appTaskImpl) { try { appTaskImpl.requestWindowingLayer(layer, createRemoteCallback(executor, callback)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private static IRemoteCallback createRemoteCallback( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback ) { return new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) { final int result = data.getInt(REMOTE_CALLBACK_RESULT_KEY); switch (result) { case RESULT_APPROVED: executor.execute(() -> callback.onResult(null)); break; case RESULT_FAILED_BAD_STATE: executor.execute(() -> callback.onError(new IllegalStateException( "The current system windowing state is not appropriate to fulfill" + " the request."))); break; default: executor.execute(() -> callback.onError(new IllegalStateException( "Unknown error, code=" + result))); break; } } }; } }
core/tests/coretests/src/android/app/OWNERS +2 −0 Original line number Diff line number Diff line # Files related to windowing per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file TaskWindowingLayerRequestHandlerTest.java = file:/services/core/java/com/android/server/wm/OWNERS # Notification, DND, Status bar per-file *Notification* = file:/packages/SystemUI/OWNERS Loading
core/tests/coretests/src/android/app/TaskWindowingLayerRequestHandlerTest.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 android.app; import static android.app.ActivityManager.AppTask.WINDOWING_LAYER_NORMAL_APP; import static android.app.ActivityManager.AppTask.WINDOWING_LAYER_PINNED; import static android.app.TaskWindowingLayerRequestHandler.REMOTE_CALLBACK_RESULT_KEY; import static android.app.TaskWindowingLayerRequestHandler.RESULT_APPROVED; import static android.app.TaskWindowingLayerRequestHandler.RESULT_FAILED_BAD_STATE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.concurrent.Executor; /** * Test class for {@link TaskWindowingLayerRequestHandler}. * * Build/Install/Run: * atest TaskWindowingLayerRequestHandlerTest */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) public class TaskWindowingLayerRequestHandlerTest { private static final Executor SAME_THREAD_EXECUTOR = Runnable::run; private IAppTask mAppTask; private OutcomeReceiver<Void, Exception> mCallback; private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private ArgumentCaptor<Exception> mExceptionCaptor; @Before public void setUp() { mAppTask = mock(IAppTask.class); mCallback = mock(OutcomeReceiver.class); mRemoteCallbackCaptor = ArgumentCaptor.forClass(IRemoteCallback.class); mExceptionCaptor = ArgumentCaptor.forClass(Exception.class); } @Test public void requestWindowingLayer_propagatesSuccessResult() throws RemoteException { final Bundle actualResult = resultWithCode(RESULT_APPROVED); TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_NORMAL_APP, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); verify(mAppTask).requestWindowingLayer(eq(WINDOWING_LAYER_NORMAL_APP), mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(actualResult); verifyCallbackReceivedSuccessResultOnce(); } @Test public void requestWindowingLayer_propagatesBadStateFailure() throws RemoteException { final Bundle actualResult = resultWithCode(RESULT_FAILED_BAD_STATE); TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_PINNED, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); verify(mAppTask).requestWindowingLayer(eq(WINDOWING_LAYER_PINNED), mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(actualResult); verifyCallbackReceivedErrorOnce(IllegalStateException.class, "The current system windowing state is not appropriate to fulfill the request."); } @Test public void requestWindowingLayer_whenInvalidResult_propagatesError() throws RemoteException { final Bundle actualResult = resultWithCode(-1); // invalid code TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_PINNED, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); verify(mAppTask).requestWindowingLayer(eq(WINDOWING_LAYER_PINNED), mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(actualResult); verifyCallbackReceivedErrorOnce(IllegalStateException.class, "Unknown error, code=-1"); } @Test(expected = RuntimeException.class) public void requestWindowingLayer_whenRemoteException_isRethrown() throws RemoteException { doThrow(new RemoteException("foo")).when(mAppTask) .requestWindowingLayer(anyInt(), any()); TaskWindowingLayerRequestHandler.requestWindowingLayer( WINDOWING_LAYER_PINNED, SAME_THREAD_EXECUTOR, mCallback, mAppTask ); } private void verifyCallbackReceivedSuccessResultOnce() { verify(mCallback).onResult(any()); verify(mCallback, never()).onError(any()); } private <E extends Exception> void verifyCallbackReceivedErrorOnce(Class<E> expectedException, String expectedMessage) { verify(mCallback, never()).onResult(any()); verify(mCallback).onError(mExceptionCaptor.capture()); Exception captured = mExceptionCaptor.getValue(); assertTrue(expectedException.isInstance(captured)); assertEquals(captured.getMessage(), expectedMessage); } private static Bundle resultWithCode(int result) { Bundle resultData = new Bundle(); resultData.putInt(REMOTE_CALLBACK_RESULT_KEY, result); return resultData; } }