Merge "Introduce Multiwindow Fullscreen API"

This commit is contained in:
Yunfan Chen
2022-12-14 05:39:43 +00:00
committed by Android (Google) Code Review
9 changed files with 334 additions and 0 deletions

View File

@@ -4292,6 +4292,7 @@ package android.app {
method @Deprecated public final void removeDialog(int);
method public void reportFullyDrawn();
method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
method public void requestFullscreenMode(@NonNull int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
method public final void requestPermissions(@NonNull String[], int);
method public final void requestShowKeyboardShortcuts();
method @Deprecated public boolean requestVisibleBehind(boolean);
@@ -4378,6 +4379,8 @@ package android.app {
field public static final int DEFAULT_KEYS_SEARCH_LOCAL = 3; // 0x3
field public static final int DEFAULT_KEYS_SHORTCUT = 2; // 0x2
field protected static final int[] FOCUSED_STATE_SET;
field public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1; // 0x1
field public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0; // 0x0
field public static final int RESULT_CANCELED = 0; // 0x0
field public static final int RESULT_FIRST_USER = 1; // 0x1
field public static final int RESULT_OK = -1; // 0xffffffff

View File

@@ -83,6 +83,7 @@ import android.os.GraphicsEnvironment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
@@ -987,6 +988,17 @@ public class Activity extends ContextThemeWrapper
/** @hide */
boolean mIsInPictureInPictureMode;
/** @hide */
@IntDef(prefix = { "FULLSCREEN_REQUEST_" }, value = {
FULLSCREEN_MODE_REQUEST_EXIT,
FULLSCREEN_MODE_REQUEST_ENTER
})
public @interface FullscreenModeRequest {}
public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0;
public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
private boolean mShouldDockBigOverlays;
private UiTranslationController mUiTranslationController;
@@ -3000,6 +3012,36 @@ public class Activity extends ContextThemeWrapper
return false;
}
/**
* Request to put the a freeform activity into fullscreen. This will only be allowed if the
* activity is on a freeform display, such as a desktop device. The requester has to be the
* top-most activity and the request should be a response to a user input. When getting
* fullscreen and receiving corresponding {@link #onConfigurationChanged(Configuration)} and
* {@link #onMultiWindowModeChanged(boolean, Configuration)}, the activity should relayout
* itself and the system bars' visibilities can be controlled as usual fullscreen apps.
*
* Calling it again with the exit request can restore the activity to the previous status.
* This will only happen when it got into fullscreen through this API.
*
* If an app wants to be in fullscreen always, it should claim as not being resizable
* by setting
* <a href="https://developer.android.com/guide/topics/large-screens/multi-window-support#resizeableActivity">
* {@code android:resizableActivity="false"}</a> instead of calling this API.
*
* @param request Can be {@link #FULLSCREEN_MODE_REQUEST_ENTER} or
* {@link #FULLSCREEN_MODE_REQUEST_EXIT} to indicate this request is to get
* fullscreen or get restored.
* @param approvalCallback Optional callback, use {@code null} when not necessary. When the
* request is approved or rejected, the callback will be triggered. This
* will happen before any configuration change. The callback will be
* dispatched on the main thread.
*/
public void requestFullscreenMode(@NonNull @FullscreenModeRequest int request,
@Nullable OutcomeReceiver<Void, Throwable> approvalCallback) {
FullscreenRequestHandler.requestFullscreenMode(
request, approvalCallback, mCurrentConfig, getActivityToken());
}
/**
* Specifies a preference to dock big overlays like the expanded picture-in-picture on TV
* (see {@link PictureInPictureParams.Builder#setExpandedAspectRatio}). Docking puts the

View File

@@ -24,6 +24,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Singleton;
@@ -372,6 +373,14 @@ public class ActivityClient {
}
}
void requestMultiwindowFullscreen(IBinder token, int request, IRemoteCallback callback) {
try {
getActivityClientController().requestMultiwindowFullscreen(token, request, callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
void startLockTaskModeByToken(IBinder token) {
try {
getActivityClientController().startLockTaskModeByToken(token);

View File

@@ -61,6 +61,12 @@ public class ActivityTaskManager {
*/
public static final int INVALID_TASK_ID = -1;
/**
* Invalid windowing mode.
* @hide
*/
public static final int INVALID_WINDOWING_MODE = -1;
/**
* Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
* that the resize doesn't need to preserve the window, and can be skipped if bounds

View File

@@ -0,0 +1,123 @@
/*
* 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 android.app;
import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.OutcomeReceiver;
/**
* @hide
*/
public class FullscreenRequestHandler {
@IntDef(prefix = { "RESULT_" }, value = {
RESULT_APPROVED,
RESULT_FAILED_NOT_IN_FREEFORM,
RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY,
RESULT_FAILED_NOT_DEFAULT_FREEFORM,
RESULT_FAILED_NOT_TOP_FOCUSED
})
public @interface RequestResult {}
public static final int RESULT_APPROVED = 0;
public static final int RESULT_FAILED_NOT_IN_FREEFORM = 1;
public static final int RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY = 2;
public static final int RESULT_FAILED_NOT_DEFAULT_FREEFORM = 3;
public static final int RESULT_FAILED_NOT_TOP_FOCUSED = 4;
public static final String REMOTE_CALLBACK_RESULT_KEY = "result";
static void requestFullscreenMode(@NonNull @Activity.FullscreenModeRequest int request,
@Nullable OutcomeReceiver<Void, Throwable> approvalCallback, Configuration config,
IBinder token) {
int earlyCheck = earlyCheckRequestMatchesWindowingMode(
request, config.windowConfiguration.getWindowingMode());
if (earlyCheck != RESULT_APPROVED) {
if (approvalCallback != null) {
notifyFullscreenRequestResult(approvalCallback, earlyCheck);
}
return;
}
try {
if (approvalCallback != null) {
ActivityClient.getInstance().requestMultiwindowFullscreen(token, request,
new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle res) {
notifyFullscreenRequestResult(
approvalCallback, res.getInt(REMOTE_CALLBACK_RESULT_KEY));
}
});
} else {
ActivityClient.getInstance().requestMultiwindowFullscreen(token, request, null);
}
} catch (Throwable e) {
if (approvalCallback != null) {
approvalCallback.onError(e);
}
}
}
private static void notifyFullscreenRequestResult(
OutcomeReceiver<Void, Throwable> callback, int result) {
Throwable e = null;
switch (result) {
case RESULT_FAILED_NOT_IN_FREEFORM:
e = new IllegalStateException("The window is not a freeform window, the request "
+ "to get into fullscreen cannot be approved.");
break;
case RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY:
e = new IllegalStateException("The window is not in fullscreen by calling the "
+ "requestFullscreenMode API before, such that cannot be restored.");
break;
case RESULT_FAILED_NOT_DEFAULT_FREEFORM:
e = new IllegalStateException("The window is not launched in freeform by default.");
break;
case RESULT_FAILED_NOT_TOP_FOCUSED:
e = new IllegalStateException("The window is not the top focused window.");
break;
default:
callback.onResult(null);
break;
}
if (e != null) {
callback.onError(e);
}
}
private static int earlyCheckRequestMatchesWindowingMode(int request, int windowingMode) {
if (request == FULLSCREEN_MODE_REQUEST_ENTER) {
if (windowingMode != WINDOWING_MODE_FREEFORM) {
return RESULT_FAILED_NOT_IN_FREEFORM;
}
} else {
if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
}
}
return RESULT_APPROVED;
}
}

View File

@@ -24,6 +24,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.PersistableBundle;
import android.view.RemoteAnimationDefinition;
import android.window.SizeConfigurationBuckets;
@@ -102,6 +103,8 @@ interface IActivityClientController {
void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
oneway void setShouldDockBigOverlays(in IBinder token, in boolean shouldDockBigOverlays);
void toggleFreeformWindowingMode(in IBinder token);
oneway void requestMultiwindowFullscreen(in IBinder token, in int request,
in IRemoteCallback callback);
oneway void startLockTaskModeByToken(in IBinder token);
oneway void stopLockTaskModeByToken(in IBinder token);

View File

@@ -619,6 +619,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
"-1484988952": {
"message": "Creating Pending Multiwindow Fullscreen Request: %s",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityClientController.java"
},
"-1483435730": {
"message": "InsetsSource setWin %s for type %s",
"level": "DEBUG",

View File

@@ -17,7 +17,15 @@
package com.android.server.wm;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
import static android.app.FullscreenRequestHandler.REMOTE_CALLBACK_RESULT_KEY;
import static android.app.FullscreenRequestHandler.RESULT_APPROVED;
import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_DEFAULT_FREEFORM;
import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FREEFORM;
import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_TOP_FOCUSED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -27,6 +35,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -52,6 +61,7 @@ import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.FullscreenRequestHandler;
import android.app.IActivityClientController;
import android.app.ICompatCameraControlCallback;
import android.app.IRequestFinishCallback;
@@ -71,6 +81,7 @@ import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -85,6 +96,7 @@ import android.window.TransitionInfo;
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -1056,6 +1068,133 @@ class ActivityClientController extends IActivityClientController.Stub {
}
}
private @FullscreenRequestHandler.RequestResult int validateMultiwindowFullscreenRequestLocked(
Task topFocusedRootTask, int fullscreenRequest, ActivityRecord requesterActivity) {
// If the mode is not by default freeform, the freeform will be a user-driven event.
if (topFocusedRootTask.getParent().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
return RESULT_FAILED_NOT_DEFAULT_FREEFORM;
}
// If this is not coming from the currently top-most activity, reject the request.
if (requesterActivity != topFocusedRootTask.getTopMostActivity()) {
return RESULT_FAILED_NOT_TOP_FOCUSED;
}
if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
return RESULT_FAILED_NOT_IN_FREEFORM;
}
} else {
if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
}
if (topFocusedRootTask.mMultiWindowRestoreWindowingMode == INVALID_WINDOWING_MODE) {
return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
}
}
return RESULT_APPROVED;
}
@Override
public void requestMultiwindowFullscreen(IBinder callingActivity, int fullscreenRequest,
IRemoteCallback callback) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
requestMultiwindowFullscreenLocked(callingActivity, fullscreenRequest, callback);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void requestMultiwindowFullscreenLocked(IBinder callingActivity, int fullscreenRequest,
IRemoteCallback callback) {
final ActivityRecord r = ActivityRecord.forTokenLocked(callingActivity);
if (r == null) {
return;
}
// If the shell transition is not enabled, just execute and done.
final TransitionController controller = r.mTransitionController;
if (!controller.isShellTransitionsEnabled()) {
final @FullscreenRequestHandler.RequestResult int validateResult;
final Task topFocusedRootTask;
topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
fullscreenRequest, r);
reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
if (validateResult == RESULT_APPROVED) {
executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
}
return;
}
// Initiate the transition.
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller,
mService.mWindowManager.mSyncEngine);
if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Creating Pending Multiwindow Fullscreen Request: %s", transition);
mService.mWindowManager.mSyncEngine.queueSyncSet(
() -> r.mTransitionController.moveToCollecting(transition),
() -> {
executeFullscreenRequestTransition(fullscreenRequest, callback, r,
transition, true /* queued */);
});
} else {
executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition,
false /* queued */);
}
}
private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback,
ActivityRecord r, Transition transition, boolean queued) {
final @FullscreenRequestHandler.RequestResult int validateResult;
final Task topFocusedRootTask;
topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
fullscreenRequest, r);
reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
if (validateResult != RESULT_APPROVED) {
if (queued) {
transition.abort();
}
return;
}
r.mTransitionController.requestStartTransition(transition, topFocusedRootTask,
null /* remoteTransition */, null /* displayChange */);
executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
transition.setReady(topFocusedRootTask, true);
}
private static void reportMultiwindowFullscreenRequestValidatingResult(IRemoteCallback callback,
@FullscreenRequestHandler.RequestResult int result) {
if (callback == null) {
return;
}
Bundle res = new Bundle();
res.putInt(REMOTE_CALLBACK_RESULT_KEY, result);
try {
callback.sendResult(res);
} catch (RemoteException e) {
Slog.w(TAG, "client throws an exception back to the server, ignore it");
}
}
private static void executeMultiWindowFullscreenRequest(int fullscreenRequest, Task requester) {
final int targetWindowingMode;
if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
requester.mMultiWindowRestoreWindowingMode =
requester.getRequestedOverrideWindowingMode();
targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
} else {
targetWindowingMode = requester.mMultiWindowRestoreWindowingMode;
requester.mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
}
requester.setWindowingMode(targetWindowingMode);
if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
requester.setBounds(null);
}
}
@Override
public void startLockTaskModeByToken(IBinder token) {
synchronized (mGlobalLock) {

View File

@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.app.ActivityManager.isStartResultSuccessful;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
@@ -443,6 +444,8 @@ class Task extends TaskFragment {
@Surface.Rotation
private int mRotation;
int mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
/**
* Last requested orientation reported to DisplayContent. This is different from {@link
* #mOrientation} in the sense that this takes activities' requested orientation into