From 2eebaf307ae99610169eb67c0302bc699901b292 Mon Sep 17 00:00:00 2001 From: "chengjian.scj" Date: Tue, 11 Feb 2025 11:36:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?Start=20the=20virtualdisplay=E2=80=98s=20de?= =?UTF-8?q?sktop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scrcpy/video/NewDisplayCapture.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 033d6b9a8c..6098238dd8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy.video; import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.Options; import com.genymobile.scrcpy.control.PositionMapper; import com.genymobile.scrcpy.device.DisplayInfo; @@ -14,6 +15,10 @@ import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.app.ActivityOptions; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.hardware.display.VirtualDisplay; import android.os.Build; @@ -166,8 +171,7 @@ public void prepare() { public void startNew(Surface surface) { int virtualDisplayId; try { - int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC - | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + int flags = VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH | VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; if (vdDestroyContent) { @@ -177,7 +181,8 @@ public void startNew(Surface surface) { flags |= VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } if (Build.VERSION.SDK_INT >= AndroidVersions.API_33_ANDROID_13) { - flags |= VIRTUAL_DISPLAY_FLAG_TRUSTED + flags |= VIRTUAL_DISPLAY_FLAG_PUBLIC + | VIRTUAL_DISPLAY_FLAG_TRUSTED | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED | VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED; @@ -191,6 +196,10 @@ public void startNew(Surface surface) { virtualDisplayId = virtualDisplay.getDisplay().getDisplayId(); Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")"); + if (Build.VERSION.SDK_INT < AndroidVersions.API_33_ANDROID_13) { + displayLauncher(virtualDisplayId); + } + displaySizeMonitor.start(virtualDisplayId, this::invalidate); } catch (Exception e) { Ln.e("Could not create display", e); @@ -258,4 +267,27 @@ private static int scaleDpi(Size initialSize, int initialDpi, Size size) { public void requestInvalidate() { invalidate(); } -} + + private void displayLauncher(int virtualDisplayId) { + PackageManager pm = FakeContext.get().getPackageManager(); + + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + ResolveInfo homeResolveInfo = (ResolveInfo) pm.resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY); + + Intent secondaryHomeIntent = new Intent(Intent.ACTION_MAIN); + secondaryHomeIntent.addCategory(Intent.CATEGORY_SECONDARY_HOME); + ResolveInfo secondaryHomeResolveInfo = (ResolveInfo) pm.resolveActivity(secondaryHomeIntent, PackageManager.MATCH_DEFAULT_ONLY); + if (secondaryHomeResolveInfo.activityInfo.packageName.equals(homeResolveInfo.activityInfo.packageName)) { + Intent launcherIntent = new Intent(); + launcherIntent.setClassName(secondaryHomeResolveInfo.activityInfo.packageName, secondaryHomeResolveInfo.activityInfo.name); + launcherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(virtualDisplayId); + + ServiceManager.getActivityManager().startActivity(launcherIntent, options.toBundle()); + } + } +} \ No newline at end of file From e96a10bb0f6de6ff740bc131e23689c0c9ee75f1 Mon Sep 17 00:00:00 2001 From: "chengjian.scj" Date: Wed, 12 Feb 2025 10:30:05 +0800 Subject: [PATCH 2/4] Change the activity launch type to home --- .../genymobile/scrcpy/video/NewDisplayCapture.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 6098238dd8..87d54e4f60 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -15,6 +15,7 @@ import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.annotation.SuppressLint; import android.app.ActivityOptions; import android.content.Intent; import android.content.pm.PackageManager; @@ -25,7 +26,9 @@ import android.view.Surface; import java.io.IOException; +import java.lang.reflect.Method; +@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"}) public class NewDisplayCapture extends SurfaceCapture { // Internal fields copied from android.hardware.display.DisplayManager @@ -281,11 +284,15 @@ private void displayLauncher(int virtualDisplayId) { if (secondaryHomeResolveInfo.activityInfo.packageName.equals(homeResolveInfo.activityInfo.packageName)) { Intent launcherIntent = new Intent(); launcherIntent.setClassName(secondaryHomeResolveInfo.activityInfo.packageName, secondaryHomeResolveInfo.activityInfo.name); - launcherIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - + ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchDisplayId(virtualDisplayId); + try { + Method method = ActivityOptions.class.getDeclaredMethod("setLaunchActivityType", int.class); + method.invoke(options, /* ACTIVITY_TYPE_HOME */ 2); + } catch(Exception e) { + Ln.e("Could not invoke method", e); + } ServiceManager.getActivityManager().startActivity(launcherIntent, options.toBundle()); } From 4026c9884e06100437fc756b88cb9fdbfada65cd Mon Sep 17 00:00:00 2001 From: "chengjian.scj" Date: Tue, 18 Feb 2025 10:42:08 +0800 Subject: [PATCH 3/4] reset the VIRTUAL_DISPLAY_FLAG_PUBLIC flag --- .../java/com/genymobile/scrcpy/video/NewDisplayCapture.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 87d54e4f60..66acf41866 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -174,7 +174,8 @@ public void prepare() { public void startNew(Surface surface) { int virtualDisplayId; try { - int flags = VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC + | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH | VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; if (vdDestroyContent) { @@ -184,8 +185,7 @@ public void startNew(Surface surface) { flags |= VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } if (Build.VERSION.SDK_INT >= AndroidVersions.API_33_ANDROID_13) { - flags |= VIRTUAL_DISPLAY_FLAG_PUBLIC - | VIRTUAL_DISPLAY_FLAG_TRUSTED + flags |= VIRTUAL_DISPLAY_FLAG_TRUSTED | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED | VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED; From 93abcff312e8697fb93cbf581bb0a15195166b60 Mon Sep 17 00:00:00 2001 From: "chengjian.scj" Date: Fri, 21 Feb 2025 14:39:17 +0800 Subject: [PATCH 4/4] Fix icon click --- server/build_without_gradle.sh | 7 + .../aidl/android/app/ActivityManager.aidl | 20 ++ .../aidl/android/app/ITaskStackListener.aidl | 230 ++++++++++++++++++ .../java/android/app/ActivityManager.java | 74 ++++++ .../scrcpy/video/NewDisplayCapture.java | 37 ++- .../scrcpy/wrappers/ActivityManager.java | 16 ++ .../scrcpy/wrappers/TaskStackListener.java | 158 ++++++++++++ 7 files changed, 537 insertions(+), 5 deletions(-) create mode 100644 server/src/main/aidl/android/app/ActivityManager.aidl create mode 100644 server/src/main/aidl/android/app/ITaskStackListener.aidl create mode 100644 server/src/main/java/android/app/ActivityManager.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/wrappers/TaskStackListener.java diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index e0b69aeebc..c2ddea5e51 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -47,6 +47,10 @@ EOF echo "Generating java from aidl..." cd "$SERVER_DIR/src/main/aidl" +"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. \ + android/app/ActivityManager.aidl +"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. -p "$ANDROID_AIDL" \ + android/app/ITaskStackListener.aidl "$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. \ android/content/IOnPrimaryClipChangedListener.aidl "$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. -p "$ANDROID_AIDL" \ @@ -54,6 +58,7 @@ cd "$SERVER_DIR/src/main/aidl" # Fake sources to expose hidden Android types to the project FAKE_SRC=( \ + android/app/*java \ android/content/*java \ ) @@ -90,6 +95,7 @@ if [[ $PLATFORM -lt 31 ]] then # use dx "$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \ + android/app/*.class \ android/view/*.class \ android/content/*.class \ ${CLASSES[@]} @@ -102,6 +108,7 @@ else # use d8 "$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \ --output "$BUILD_DIR/classes.zip" \ + android/app/*.class \ android/view/*.class \ android/content/*.class \ ${CLASSES[@]} diff --git a/server/src/main/aidl/android/app/ActivityManager.aidl b/server/src/main/aidl/android/app/ActivityManager.aidl new file mode 100644 index 0000000000..1809f217b2 --- /dev/null +++ b/server/src/main/aidl/android/app/ActivityManager.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014, 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; + +parcelable ActivityManager.RunningTaskInfo; +parcelable ActivityManager.TaskSnapshot; \ No newline at end of file diff --git a/server/src/main/aidl/android/app/ITaskStackListener.aidl b/server/src/main/aidl/android/app/ITaskStackListener.aidl new file mode 100644 index 0000000000..fe7fb53b26 --- /dev/null +++ b/server/src/main/aidl/android/app/ITaskStackListener.aidl @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2014, 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 android.app.ActivityManager; +import android.content.ComponentName; + +oneway interface ITaskStackListener { + /** Activity was resized to be displayed in split-screen. */ + const int FORCED_RESIZEABLE_REASON_SPLIT_SCREEN = 1; + /** Activity was resized to be displayed on a secondary display. */ + const int FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY = 2; + + /** Called whenever there are changes to the state of tasks in a stack. */ + void onTaskStackChanged(); + + /** Called whenever an Activity is moved to the pinned stack from another stack. */ + void onActivityPinned(String packageName, int userId, int taskId, int stackId); + + /** Called whenever an Activity is moved from the pinned stack to another stack. */ + void onActivityUnpinned(); + + /** + * Called whenever IActivityManager.startActivity is called on an activity that is already + * running, but the task is either brought to the front or a new Intent is delivered to it. + * + * @param task information about the task the activity was relaunched into + * @param homeVisible whether or not the home task is visible + * @param clearedTask whether or not the launch activity also cleared the task as a part of + * starting + * @param wasVisible whether the activity was visible before the restart attempt + */ + void onActivityRestartAttempt(in ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, + boolean clearedTask, boolean wasVisible); + + /** + * Called when we launched an activity that we forced to be resizable. + * + * @param packageName Package name of the top activity in the task. + * @param taskId Id of the task. + * @param reason {@link #FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or + * {@link #FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY}. + */ + void onActivityForcedResizable(String packageName, int taskId, int reason); + + /** + * Called when we launched an activity that dismissed the docked stack. + */ + void onActivityDismissingDockedStack(); + + /** + * Called when an activity was requested to be launched on a secondary display but was not + * allowed there. + * + * @param taskInfo info about the Activity's task + * @param requestedDisplayId the id of the requested launch display + */ + void onActivityLaunchOnSecondaryDisplayFailed(in ActivityManager.RunningTaskInfo taskInfo, + int requestedDisplayId); + + /** + * Called when an activity was requested to be launched on a secondary display but was rerouted + * to default display. + * + * @param taskInfo info about the Activity's task + * @param requestedDisplayId the id of the requested launch display + */ + void onActivityLaunchOnSecondaryDisplayRerouted(in ActivityManager.RunningTaskInfo taskInfo, + int requestedDisplayId); + + /** + * Called when a task is added. + * + * @param taskId id of the task. + * @param componentName of the activity that the task is being started with. + */ + void onTaskCreated(int taskId, in ComponentName componentName); + + /** + * Called when a task is removed. + * + * @param taskId id of the task. + */ + void onTaskRemoved(int taskId); + + /** + * Called when a task is moved to the front of its stack. + * + * @param taskInfo info about the task which moved + */ + void onTaskMovedToFront(in ActivityManager.RunningTaskInfo taskInfo); + + /** + * Called when a task’s description is changed due to an activity calling + * ActivityManagerService.setTaskDescription + * + * @param taskInfo info about the task which changed, with {@link TaskInfo#taskDescription} + */ + void onTaskDescriptionChanged(in ActivityManager.RunningTaskInfo taskInfo); + + /** + * Called when a activity’s orientation is changed due to it calling + * ActivityManagerService.setRequestedOrientation + * + * @param taskId id of the task that the activity is in. + * @param requestedOrientation the new requested orientation. + */ + void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation); + + /** + * Called when the task is about to be finished but before its surfaces are + * removed from the window manager. This allows interested parties to + * perform relevant animations before the window disappears. + * + * @param taskInfo info about the task being removed + */ + void onTaskRemovalStarted(in ActivityManager.RunningTaskInfo taskInfo); + + /** + * Called when the task has been put in a locked state because one or more of the + * activities inside it belong to a managed profile user, and that user has just + * been locked. + */ + void onTaskProfileLocked(int taskId, int userId); + + /** + * Called when a task snapshot got updated. + */ + void onTaskSnapshotChanged(int taskId, in ActivityManager.TaskSnapshot snapshot); + + /** + * Called when the resumed activity is in size compatibility mode and its override configuration + * is different from the current one of system. + * + * @param displayId Id of the display where the activity resides. + * @param activityToken Token of the size compatibility mode activity. It will be null when + * switching to a activity that is not in size compatibility mode or the + * configuration of the activity. + * @see com.android.server.wm.ActivityRecord#inSizeCompatMode + */ + void onSizeCompatModeActivityChanged(int displayId, in IBinder activityToken); + + /** + * Reports that an Activity received a back key press when there were no additional activities + * on the back stack. + * + * @param taskInfo info about the task which received the back press + */ + void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo); + + /* + * Called when contents are drawn for the first time on a display which can only contain one + * task. + * + * @param displayId the id of the display on which contents are drawn. + */ + void onSingleTaskDisplayDrawn(int displayId); + + /* + * Called when the last task is removed from a display which can only contain one task. + * + * @param displayId the id of the display from which the window is removed. + */ + void onSingleTaskDisplayEmpty(int displayId); + + /** + * Called when a task is reparented to a stack on a different display. + * + * @param taskId id of the task which was moved to a different display. + * @param newDisplayId id of the new display. + */ + void onTaskDisplayChanged(int taskId, int newDisplayId); + + /** + * Called when any additions or deletions to the recent tasks list have been made. + */ + void onRecentTaskListUpdated(); + + /** + * Called when Recent Tasks list is frozen or unfrozen. + * + * @param frozen if true, Recents Tasks list is currently frozen, false otherwise + */ + void onRecentTaskListFrozenChanged(boolean frozen); + + /** + * Called when a task gets or loses focus. + * + * @param taskId id of the task. + * @param {@code true} if the task got focus, {@code false} if it lost it. + */ + void onTaskFocusChanged(int taskId, boolean focused); + + /** + * Called when a task changes its requested orientation. It is different from {@link + * #onActivityRequestedOrientationChanged(int, int)} in the sense that this method is called + * when a task changes requested orientation due to activity launch, dimiss or reparenting. + * + * @param taskId id of the task. + * @param requestedOrientation the new requested orientation of this task as screen orientations + * in {@link android.content.pm.ActivityInfo}. + */ + void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation); + + /** + * Called when a rotation is about to start on the foreground activity. + * This applies for: + * * free sensor rotation + * * forced rotation + * * rotation settings set through adb command line + * * rotation that occurs when rotation tile is toggled in quick settings + * + * @param displayId id of the display where activity will rotate + */ + void onActivityRotation(int displayId); +} \ No newline at end of file diff --git a/server/src/main/java/android/app/ActivityManager.java b/server/src/main/java/android/app/ActivityManager.java new file mode 100644 index 0000000000..908866f4f9 --- /dev/null +++ b/server/src/main/java/android/app/ActivityManager.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 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 android.os.Parcel; +import android.os.Parcelable; + + public class ActivityManager { + public static class RunningTaskInfo extends TaskInfo implements Parcelable { + public RunningTaskInfo() { + } + + private RunningTaskInfo(Parcel source) { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + public static final Creator CREATOR = new Creator() { + public RunningTaskInfo createFromParcel(Parcel source) { + return new RunningTaskInfo(source); + } + public RunningTaskInfo[] newArray(int size) { + return new RunningTaskInfo[size]; + } + }; + } + + public static class TaskSnapshot implements Parcelable { + public TaskSnapshot() { + } + + private TaskSnapshot(Parcel source) { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + public static final Creator CREATOR = new Creator() { + public TaskSnapshot createFromParcel(Parcel source) { + return new TaskSnapshot(source); + } + public TaskSnapshot[] newArray(int size) { + return new TaskSnapshot[size]; + } + }; + } + } \ No newline at end of file diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 66acf41866..5824325715 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -14,9 +14,11 @@ import com.genymobile.scrcpy.util.AffineMatrix; import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.wrappers.ServiceManager; +import com.genymobile.scrcpy.wrappers.TaskStackListener; import android.annotation.SuppressLint; import android.app.ActivityOptions; +import android.app.ITaskStackListener; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -46,6 +48,7 @@ public class NewDisplayCapture extends SurfaceCapture { private static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15; private final VirtualDisplayListener vdListener; + private ITaskStackListener taskStackListener; private final NewDisplay newDisplay; private final DisplaySizeMonitor displaySizeMonitor = new DisplaySizeMonitor(); @@ -247,6 +250,11 @@ public void release() { virtualDisplay.release(); virtualDisplay = null; } + + if (taskStackListener != null) { + ServiceManager.getActivityManager().unregisterTaskStackListener(taskStackListener); + taskStackListener = null; + } } @Override @@ -280,11 +288,9 @@ private void displayLauncher(int virtualDisplayId) { Intent secondaryHomeIntent = new Intent(Intent.ACTION_MAIN); secondaryHomeIntent.addCategory(Intent.CATEGORY_SECONDARY_HOME); + secondaryHomeIntent.addCategory(Intent.CATEGORY_DEFAULT); ResolveInfo secondaryHomeResolveInfo = (ResolveInfo) pm.resolveActivity(secondaryHomeIntent, PackageManager.MATCH_DEFAULT_ONLY); - if (secondaryHomeResolveInfo.activityInfo.packageName.equals(homeResolveInfo.activityInfo.packageName)) { - Intent launcherIntent = new Intent(); - launcherIntent.setClassName(secondaryHomeResolveInfo.activityInfo.packageName, secondaryHomeResolveInfo.activityInfo.name); - + if (secondaryHomeResolveInfo.activityInfo.packageName.equals(homeResolveInfo.activityInfo.packageName)) { ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchDisplayId(virtualDisplayId); try { @@ -294,7 +300,28 @@ private void displayLauncher(int virtualDisplayId) { Ln.e("Could not invoke method", e); } - ServiceManager.getActivityManager().startActivity(launcherIntent, options.toBundle()); + ServiceManager.getActivityManager().startActivity(secondaryHomeIntent, options.toBundle()); + + if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12) { + taskStackListener = new TaskStackListener() { + @Override + public void onActivityLaunchOnSecondaryDisplayFailed(android.app.ActivityManager.RunningTaskInfo taskInfo, + int requestedDisplayId) { + if (requestedDisplayId == virtualDisplayId) { + String packageName = taskInfo.baseIntent.getComponent().getPackageName(); + String className = taskInfo.baseIntent.getComponent().getClassName(); + + Intent launcherIntent = new Intent(); + launcherIntent.setClassName(packageName, className); + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(virtualDisplayId); + ServiceManager.getActivityManager().startActivity(launcherIntent, options.toBundle()); + } + } + }; + + ServiceManager.getActivityManager().registerTaskStackListener(taskStackListener); + } } } } \ No newline at end of file diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java index 255483c602..7bc4a2db2a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java @@ -6,6 +6,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.ITaskStackListener; import android.content.IContentProvider; import android.content.Intent; import android.os.Binder; @@ -162,4 +163,19 @@ public void forceStopPackage(String packageName) { Ln.e("Could not invoke method", e); } } + + public void registerTaskStackListener(ITaskStackListener listener) { + try { + manager.getClass().getMethod("registerTaskStackListener", ITaskStackListener.class).invoke(manager, listener); + } catch (Exception e) { + Ln.e("Could not register task stack listener", e); + } + } + public void unregisterTaskStackListener(ITaskStackListener listener) { + try { + manager.getClass().getMethod("unregisterTaskStackListener", ITaskStackListener.class).invoke(manager, listener); + } catch (Exception e) { + Ln.e("Could not register task stack listener", e); + } + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/TaskStackListener.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/TaskStackListener.java new file mode 100644 index 0000000000..dac3e4b3a2 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/TaskStackListener.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2014, 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.genymobile.scrcpy.wrappers; + +import android.app.ActivityManager; +import android.app.ITaskStackListener; +import android.content.ComponentName; +import android.os.IBinder; + +public class TaskStackListener extends ITaskStackListener.Stub { + @Override + public void onTaskStackChanged() { + // empty default implementation + } + + @Override + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { + // empty default implementation + } + + @Override + public void onActivityUnpinned() { + // empty default implementation + } + + @Override + public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, + boolean clearedTask, boolean wasVisible) { + // empty default implementation + } + + @Override + public void onActivityForcedResizable(String packageName, int taskId, int reason) { + // empty default implementation + } + + @Override + public void onActivityDismissingDockedStack() { + // empty default implementation + } + + @Override + public void onActivityLaunchOnSecondaryDisplayFailed(ActivityManager.RunningTaskInfo taskInfo, + int requestedDisplayId) { + // empty default implementation + } + + @Override + public void onActivityLaunchOnSecondaryDisplayRerouted(ActivityManager.RunningTaskInfo taskInfo, + int requestedDisplayId) { + // empty default implementation + } + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) { + // empty default implementation + } + + @Override + public void onTaskRemoved(int taskId) { + // empty default implementation + } + + @Override + public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + // empty default implementation + } + + @Override + public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) { + // empty default implementation + } + + @Override + public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { + // empty default implementation + } + + @Override + public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo) { + // empty default implementation + } + + + @Override + public void onTaskProfileLocked(int taskId, int userId) { + // empty default implementation + } + + @Override + public void onTaskSnapshotChanged(int taskId, ActivityManager.TaskSnapshot snapshot) { + // empty default implementation + } + + @Override + public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { + // empty default implementation + } + + @Override + public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { + // empty default implementation + } + + @Override + public void onSingleTaskDisplayDrawn(int displayId) { + // empty default implementation + } + + @Override + public void onSingleTaskDisplayEmpty(int displayId) { + // empty default implementation + } + + @Override + public void onTaskDisplayChanged(int taskId, int newDisplayId) { + // empty default implementation + } + + @Override + public void onRecentTaskListUpdated() { + // empty default implementation + } + + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + // empty default implementation + } + + @Override + public void onTaskFocusChanged(int taskId, boolean focused) { + // empty default implementation + } + + @Override + public void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) { + // empty default implementation + } + + @Override + public void onActivityRotation(int displayId) { + // empty default implementation + } +} \ No newline at end of file