diff options
Diffstat (limited to 'libs')
322 files changed, 10582 insertions, 2559 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java index bfccb29bc952..e3a1d8ac48e2 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java @@ -142,6 +142,19 @@ class BackupHelper { } } + void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) { + // Clean-up the legacy states in the system + for (int i = mTaskFragmentInfos.size() - 1; i >= 0; i--) { + final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i); + mPresenter.deleteTaskFragment(wct, info.getFragmentToken()); + } + mPresenter.setSavedState(new Bundle()); + + mParcelableTaskContainerDataList.clear(); + mTaskFragmentInfos.clear(); + mTaskFragmentParentInfos.clear(); + } + boolean hasPendingStateToRestore() { return !mParcelableTaskContainerDataList.isEmpty(); } @@ -196,6 +209,7 @@ class BackupHelper { mController.onTaskFragmentParentRestored(wct, taskContainer.getTaskId(), mTaskFragmentParentInfos.get(taskContainer.getTaskId())); + mTaskFragmentParentInfos.remove(taskContainer.getTaskId()); restoredAny = true; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index db4bb0e5e75e..8345b409ae52 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -56,6 +56,7 @@ import static androidx.window.extensions.embedding.TaskFragmentContainer.Overlay import android.annotation.CallbackExecutor; import android.app.Activity; import android.app.ActivityClient; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.AppGlobals; @@ -280,7 +281,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mSplitRules.clear(); mSplitRules.addAll(rules); - if (!Flags.aeBackStackRestore() || !mPresenter.isRebuildTaskContainersNeeded()) { + if (!Flags.aeBackStackRestore() || !mPresenter.isWaitingToRebuildTaskContainers()) { return; } @@ -2893,6 +2894,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } synchronized (mLock) { + if (mPresenter.isWaitingToRebuildTaskContainers()) { + Log.w(TAG, "Rebuilding aborted, clean up and restart"); + + // Retrieve the Task intent. + final int taskId = getTaskId(activity); + Intent taskIntent = null; + final ActivityManager am = activity.getSystemService(ActivityManager.class); + final List<ActivityManager.AppTask> appTasks = am.getAppTasks(); + for (ActivityManager.AppTask appTask : appTasks) { + if (appTask.getTaskInfo().taskId == taskId) { + taskIntent = appTask.getTaskInfo().baseIntent.cloneFilter(); + break; + } + } + + // Clean up and abort the restoration + // TODO(b/369488857): also to remove the non-organized activities in the Task? + final TransactionRecord transactionRecord = + mTransactionManager.startNewTransaction(); + final WindowContainerTransaction wct = transactionRecord.getTransaction(); + mPresenter.abortTaskContainerRebuilding(wct); + transactionRecord.apply(false /* shouldApplyIndependently */); + + // Start the Task root activity. + if (taskIntent != null) { + activity.startActivity(taskIntent); + } + return; + } + final IBinder activityToken = activity.getActivityToken(); final IBinder initialTaskFragmentToken = getTaskFragmentTokenFromActivityClientRecord(activity); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 0c0ded9bad74..b498ee2ff438 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -187,10 +187,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { mBackupHelper.scheduleBackup(); } - boolean isRebuildTaskContainersNeeded() { + boolean isWaitingToRebuildTaskContainers() { return mBackupHelper.hasPendingStateToRestore(); } + void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) { + mBackupHelper.abortTaskContainerRebuilding(wct); + } + boolean rebuildTaskContainers(@NonNull WindowContainerTransaction wct, @NonNull Set<EmbeddingRule> rules) { return mBackupHelper.rebuildTaskContainers(wct, rules); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index dcc2d93060c9..b453f1d4e936 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -156,7 +156,7 @@ class TaskContainer { mSplitController = splitController; for (ParcelableTaskFragmentContainerData tfData : data.getParcelableTaskFragmentContainerDataList()) { - final TaskFragmentInfo info = taskFragmentInfoMap.get(tfData.mToken); + final TaskFragmentInfo info = taskFragmentInfoMap.remove(tfData.mToken); if (info != null && !info.isEmpty()) { final TaskFragmentContainer container = new TaskFragmentContainer(tfData, splitController, this); diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index f8574294a3a2..4642fe59bcb2 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -197,6 +197,7 @@ java_library { android_library { name: "WindowManager-Shell", srcs: [ + "src/com/android/wm/shell/EventLogTags.logtags", ":wm_shell_protolog_src", // TODO(b/168581922) protologtool do not support kotlin(*.kt) ":wm_shell-sources-kt", @@ -220,6 +221,7 @@ android_library { "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "//frameworks/libs/systemui:iconloader_base", "com_android_wm_shell_flags_lib", + "PlatformAnimationLib", "WindowManager-Shell-proto", "WindowManager-Shell-lite-proto", "WindowManager-Shell-shared", @@ -232,10 +234,6 @@ android_library { // *.kt sources are inside a filegroup. "kotlin-annotations", ], - required: [ - "wmshell.protolog.json.gz", - "wmshell.protolog.pb", - ], flags_packages: [ "com_android_wm_shell_flags", ], @@ -244,3 +242,11 @@ android_library { plugins: ["dagger2-compiler"], use_resource_processor: true, } + +java_defaults { + name: "wmshell_defaults", + required: [ + "wmshell.protolog.json.gz", + "wmshell.protolog.pb", + ], +} diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index 3b739c3d5817..1260796810c2 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -24,6 +24,7 @@ <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" /> + <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" /> <application> <activity diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 63a288079401..cf0a975b6c30 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -156,6 +156,13 @@ flag { } flag { + name: "enable_flexible_two_app_split" + namespace: "multitasking" + description: "Enables only 2 app 90:10 split" + bug: "349828130" +} + +flag { name: "enable_flexible_split" namespace: "multitasking" description: "Enables flexibile split feature for split screen" diff --git a/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml new file mode 100644 index 000000000000..0f9b28a07bde --- /dev/null +++ b/libs/WindowManager/Shell/res/color/open_by_default_settings_dialog_radio_button_color.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:state_checked="true" + android:color="?androidprv:attr/materialColorPrimaryContainer"/> + <item android:color="?androidprv:attr/materialColorSurfaceContainer"/> +</selector> diff --git a/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml new file mode 100644 index 000000000000..b74d92221061 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/textColorTertiary" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/system_on_tertiary_container_light" + android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620Z" /> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml new file mode 100644 index 000000000000..f3800e05148e --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" + android:width="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000000" + android:pathData="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml new file mode 100644 index 000000000000..5260450e8a13 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M240,840L240,720L120,720L120,640L320,640L320,840L240,840ZM640,840L640,640L840,640L840,720L720,720L720,840L640,840ZM120,320L120,240L240,240L240,120L320,120L320,320L120,320ZM640,320L640,120L720,120L720,240L840,240L840,320L640,320Z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml new file mode 100644 index 000000000000..4070c3d577d8 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_by_default_settings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/black" + android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml new file mode 100644 index 000000000000..a12a74658953 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="rectangle"> + <corners android:radius="30dp" /> + <solid android:color="@android:color/system_tertiary_fixed" /> + </shape> + </item> +</layer-list> diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml new file mode 100644 index 000000000000..aadffb5a0003 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> + +<!-- An arrow that points towards left. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="10dp" + android:height="12dp" + android:viewportWidth="10" + android:viewportHeight="12"> + <path + android:pathData="M2.858,4.285C1.564,5.062 1.564,6.938 2.858,7.715L10,12L10,0L2.858,4.285Z" + android:fillColor="@android:color/system_tertiary_fixed"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml new file mode 100644 index 000000000000..e3c9a662671e --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<!-- An arrow that points upwards. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="12dp" + android:height="9dp" + android:viewportWidth="12" + android:viewportHeight="9"> + <path + android:pathData="M7.715,1.858C6.938,0.564 5.062,0.564 4.285,1.858L0,9L12,9L7.715,1.858Z" + android:fillColor="@android:color/system_tertiary_fixed"/> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml new file mode 100644 index 000000000000..4eb22712f5e1 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<shape android:shape="rectangle" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/> + <corners android:radius="28dp"/> +</shape> diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_confirm_button_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_confirm_button_background.xml new file mode 100644 index 000000000000..2b2e9df07dce --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_confirm_button_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="?androidprv:attr/materialColorPrimary"/> + <corners android:radius="50dp"/> +</shape> diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml new file mode 100644 index 000000000000..1ac952bf9112 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_radio_buttons_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<shape android:shape="rectangle" + xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/open_by_default_settings_dialog_radio_button_color"/> + <corners android:radius="16dp"/> +</shape> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index 6913e54c2b10..aeb734e2d2d3 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -165,17 +165,28 @@ android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height" android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin" android:layout_marginStart="1dp" - android:orientation="vertical" + android:orientation="horizontal" android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation" android:background="@drawable/desktop_mode_decor_handle_menu_background"> <Button android:id="@+id/open_in_browser_button" + android:layout_weight="1" android:contentDescription="@string/open_in_browser_text" android:text="@string/open_in_browser_text" android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser" android:drawableTint="?androidprv:attr/materialColorOnSurface" style="@style/DesktopModeHandleMenuActionButton"/> + + <ImageButton + android:id="@+id/open_by_default_button" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_gravity="end|center_vertical" + android:layout_marginEnd="16dp" + android:contentDescription="@string/open_by_default_settings_text" + android:src="@drawable/desktop_mode_ic_handle_menu_open_by_default_settings" + android:tint="?androidprv:attr/materialColorOnSurface"/> </LinearLayout> </LinearLayout> diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_left_arrow_tooltip.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_left_arrow_tooltip.xml new file mode 100644 index 000000000000..a269b9ee1dd5 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_left_arrow_tooltip.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:elevation="1dp" + android:orientation="horizontal"> + + <!-- ImageView for the arrow icon, positioned horizontally at the start of the tooltip + container. --> + <ImageView + android:id="@+id/arrow_icon" + android:layout_width="10dp" + android:layout_height="12dp" + android:layout_gravity="center_vertical" + android:src="@drawable/desktop_windowing_education_tooltip_left_arrow" /> + + <!-- Layout for the tooltip, excluding the arrow. Separating the tooltip content from the arrow + allows scaling of only the tooltip container when the content changes, without affecting the + arrow. --> + <include layout="@layout/desktop_windowing_education_tooltip_container" /> +</LinearLayout> diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml new file mode 100644 index 000000000000..09a049c060eb --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tooltip_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/desktop_windowing_education_tooltip_background" + android:orientation="horizontal" + android:padding="@dimen/desktop_windowing_education_tooltip_padding"> + + <ImageView + android:id="@+id/tooltip_icon" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_gravity="center_vertical" + android:src="@drawable/app_handle_education_tooltip_icon" /> + + <TextView + android:id="@+id/tooltip_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="2dp" + android:lineHeight="20dp" + android:maxWidth="150dp" + android:textColor="@android:color/system_on_tertiary_container_light" + android:textFontWeight="500" + android:textSize="14sp" /> +</LinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_top_arrow_tooltip.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_top_arrow_tooltip.xml new file mode 100644 index 000000000000..c73c1dad0e18 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_top_arrow_tooltip.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:elevation="1dp" + android:orientation="vertical"> + + <!-- ImageView for the arrow icon, positioned vertically above the tooltip container. --> + <ImageView + android:id="@+id/arrow_icon" + android:layout_width="12dp" + android:layout_height="9dp" + android:layout_gravity="center_horizontal" + android:src="@drawable/desktop_windowing_education_tooltip_top_arrow" /> + + <!-- Layout for the tooltip, excluding the arrow. Separating the tooltip content from the arrow + allows scaling of only the tooltip container when the content changes, without affecting the + arrow. --> + <include layout="@layout/desktop_windowing_education_tooltip_container" /> +</LinearLayout> diff --git a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml new file mode 100644 index 000000000000..b5bceda9a623 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<com.android.wm.shell.apptoweb.OpenByDefaultDialogView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + style="@style/LetterboxDialog"> + + <!-- The background of the top-level layout acts as the background dim. --> + <FrameLayout + android:id="@+id/open_by_default_dialog_container" + android:layout_width="@dimen/open_by_default_settings_dialog_width" + android:layout_height="wrap_content" + android:background="@drawable/open_by_default_settings_dialog_background" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + + <!-- The ScrollView should only wrap the content of the dialog, otherwise the background + corner radius will be cut off when scrolling to the top/bottom. --> + <ScrollView + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:padding="24dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical"> + + <ImageView + android:id="@+id/application_icon" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_gravity="center_horizontal" + android:importantForAccessibility="no" + android:layout_marginTop="24dp" + android:layout_marginBottom="16dp" + android:scaleType="centerCrop"/> + + <TextView + android:id="@+id/application_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="8dp" + android:lineHeight="32dp" + android:textFontWeight="400" + android:textSize="24sp" + android:textColor="?androidprv:attr/materialColorOnSurfaceVariant" + tools:text="Gmail" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="12sp" + android:textFontWeight="400" + android:lineHeight="16dp" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="16dp" + android:textColor="?androidprv:attr/materialColorOnSurfaceVariant" + android:text="@string/open_by_default_dialog_subheader_text"/> + + <RadioGroup + android:id="@+id/open_by_default_radio_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal"> + <RadioButton + android:id="@+id/open_in_app_button" + android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width" + android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height" + android:paddingStart="20dp" + android:paddingEnd="0dp" + android:layout_marginHorizontal="16dp" + android:layout_marginBottom="4dp" + android:text="@string/open_by_default_dialog_in_app_text" + android:textFontWeight="500" + android:textSize="16sp" + android:lineHeight="24dp" + android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/> + <RadioButton + android:id="@+id/open_in_browser_button" + android:layout_width="@dimen/open_by_default_settings_dialog_radio_button_width" + android:layout_height="@dimen/open_by_default_settings_dialog_radio_button_height" + android:paddingStart="20dp" + android:paddingEnd="0dp" + android:layout_marginStart="16dp" + android:text="@string/open_by_default_dialog_in_browser_text" + android:textFontWeight="500" + android:textSize="16sp" + android:lineHeight="24dp" + android:background="@drawable/open_by_default_settings_dialog_radio_buttons_background"/> + </RadioGroup> + + <Button + android:id="@+id/open_by_default_settings_dialog_confirm_button" + android:layout_width="wrap_content" + android:layout_height="36dp" + android:text="@string/open_by_default_dialog_dismiss_button_text" + android:layout_gravity="end" + android:layout_marginHorizontal="24dp" + android:layout_marginTop="32dp" + android:layout_marginBottom="24dp" + android:textSize="14sp" + android:textFontWeight="500" + android:textColor="?androidprv:attr/materialColorOnPrimary" + android:background="@drawable/open_by_default_settings_dialog_confirm_button_background"/> + </LinearLayout> + </ScrollView> + </FrameLayout> +</com.android.wm.shell.apptoweb.OpenByDefaultDialogView> + diff --git a/libs/WindowManager/Shell/res/layout/pip2_menu_action.xml b/libs/WindowManager/Shell/res/layout/pip2_menu_action.xml new file mode 100644 index 000000000000..04ece31cb820 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/pip2_menu_action.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 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. +--> +<com.android.wm.shell.pip2.phone.PipMenuActionView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:background="?android:selectableItemBackgroundBorderless" + android:forceHasOverlappingRendering="false"> + + <ImageView + android:id="@+id/custom_close_bg" + android:layout_width="@dimen/pip_custom_close_bg_size" + android:layout_height="@dimen/pip_custom_close_bg_size" + android:layout_gravity="center" + android:src="@drawable/pip_custom_close_bg" + android:visibility="gone"/> + + <ImageView + android:id="@+id/image" + android:layout_width="@dimen/pip_action_inner_size" + android:layout_height="@dimen/pip_action_inner_size" + android:layout_gravity="center" + android:scaleType="fitXY"/> + +</com.android.wm.shell.pip2.phone.PipMenuActionView> diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 50aa4ca1a1f1..582c1a22a203 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerakwessies?\nTik om aan te pas"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nie opgelos nie?\nTik om terug te stel"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen kamerakwessies nie? Tik om toe te maak."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tik om die appkieslys oop te maak"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tik om verskeie apps saam te wys"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Keer terug na volskerm van die appkieslys af"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app in vir verdeelde skerm"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Bestuur vensters"</string> <string name="close_text" msgid="4986518933445178928">"Maak toe"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Maak kieslys oop"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gryp skerm vas"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Hierdie app se grootte kan nie verander word nie"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App kan nie hierheen geskuif word nie"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimeer"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Spring na links"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Spring na regs"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 281e313d328f..0798c9a8fbe0 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"የካሜራ ችግሮች አሉ?\nዳግም ለማበጀት መታ ያድርጉ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"አልተስተካከለም?\nለማህደር መታ ያድርጉ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ምንም የካሜራ ችግሮች የሉም? ለማሰናበት መታ ያድርጉ።"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"የመተግበሪያ ምናሌውን ለመክፈት መታ ያድርጉ"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"በርካታ መተግበሪያዎችን በአንድ ላይ ለማየት መታ ያድርጉ"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ከመተግበሪያ ምናሌው ወደ ሙሉ ማያ ገፅ ይመለሱ"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ተጨማሪ ይመልከቱ እና ያድርጉ"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ለተከፈለ ማያ ገፅ ሌላ መተግበሪያ ይጎትቱ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጭ ሁለቴ መታ ያድርጉ"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"መስኮቶችን አስተዳድር"</string> <string name="close_text" msgid="4986518933445178928">"ዝጋ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ምናሌን ክፈት"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ማያ ገጹን አሳድግ"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ይህ መተግበሪያ መጠኑ ሊቀየር አይችልም"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"አሳድግ"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ወደ ግራ አሳድግ"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ወደ ቀኝ አሳድግ"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 2c0b34a604a9..9dcb5ec26273 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"هل هناك مشاكل في الكاميرا؟\nانقر لإعادة الضبط."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ألم يتم حل المشكلة؟\nانقر للعودة"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"أليس هناك مشاكل في الكاميرا؟ انقر للإغلاق."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"انقر لفتح قائمة التطبيق"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"انقر لعرض عدة تطبيقات معًا"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"الرجوع إلى وضع ملء الشاشة من قائمة التطبيق"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"استخدام تطبيقات متعدّدة في وقت واحد"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسحب تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"إدارة النوافذ"</string> <string name="close_text" msgid="4986518933445178928">"إغلاق"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"فتح القائمة"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"التقاط صورة للشاشة"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"لا يمكن تغيير حجم نافذة هذا التطبيق"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"لا يمكن نقل التطبيق إلى هنا"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"تكبير"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"المحاذاة إلى اليسار"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"المحاذاة إلى اليمين"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index c5e37162e2d9..484eef53e087 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"কেমেৰাৰ কোনো সমস্যা হৈছে নেকি?\nপুনৰ খাপ খোৱাবলৈ টিপক"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এইটো সমাধান কৰা নাই নেকি?\nপূৰ্বাৱস্থালৈ নিবলৈ টিপক"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"কেমেৰাৰ কোনো সমস্যা নাই নেকি? অগ্ৰাহ্য কৰিবলৈ টিপক।"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"এপৰ মেনুখন খুলিবলৈ টিপক"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"একাধিক এপ্ একেলগে দেখুৱাবলৈ টিপক"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"এপৰ মেনুখনৰ পৰা পূৰ্ণ স্ক্ৰীনলৈ উভতি যাওক"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"চাওক আৰু অধিক কৰক"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"বিভাজিত স্ক্ৰীনৰ বাবে অন্য এটা এপ্ টানি আনি এৰক"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ৱিণ্ড’ পৰিচালনা কৰক"</string> <string name="close_text" msgid="4986518933445178928">"বন্ধ কৰক"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খোলক"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্ৰীন স্নেপ কৰক"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"এই এপ্টোৰ আকাৰ সলনি কৰিব নোৱাৰি"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ইয়ালৈ এপ্টো আনিব নোৱাৰি"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"মেক্সিমাইজ কৰক"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"বাওঁফাললৈ স্নেপ কৰক"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"সোঁফাললৈ স্নেপ কৰক"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index e23e8d049a8f..ded6da80464d 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera problemi var?\nBərpa etmək üçün toxunun"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Düzəltməmisiniz?\nGeri qaytarmaq üçün toxunun"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera problemi yoxdur? Qapatmaq üçün toxunun."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tətbiq menyusunu açmaq üçün toxunun"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Bir neçə tətbiqi birlikdə göstərmək üçün toxunun"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Tətbiq menyusundan tam ekrana qayıdın"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ardını görün və edin"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran üçün başqa tətbiq sürüşdürün"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Pəncərələri idarə edin"</string> <string name="close_text" msgid="4986518933445178928">"Bağlayın"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyunu açın"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranı çəkin"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Bu tətbiqin ölçüsünü dəyişmək olmur"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Tətbiqi bura köçürmək mümkün deyil"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Böyüdün"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Sola tərəf çəkin"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Sağa tərəf çəkin"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index 283d1f5578cf..415547c790f4 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Imate problema sa kamerom?\nDodirnite da biste ponovo uklopili"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije rešen?\nDodirnite da biste vratili"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema sa kamerom? Dodirnite da biste odbacili."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Dodirnite da biste otvorili meni aplikacije"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Dodirnite da biste prikazali više aplikacija zajedno"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Vratite se iz menija aplikacije na prikaz preko celog ekrana"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vidite i uradite više"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite drugu aplikaciju da biste koristili podeljeni ekran"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Upravljajte prozorima"</string> <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvorite meni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Uklopi ekran"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Veličina ove aplikacije ne može da se promeni"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija ne može da se premesti ovde"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Uvećajte"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Prikačite levo"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Prikačite desno"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 48914f1eb27c..aded647d43a1 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Праблемы з камерай?\nНацісніце, каб пераабсталяваць"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не ўдалося выправіць?\nНацісніце, каб аднавіць"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ніякіх праблем з камерай? Націсніце, каб адхіліць."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Адкрыць меню праграмы"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Паказаць некалькі праграм разам"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Вярнуцца ў поўнаэкранны рэжым з меню праграмы"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Адначасова выконвайце розныя задачы"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перацягніце іншую праграму, каб выкарыстоўваць падзелены экран"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Кіраваць вокнамі"</string> <string name="close_text" msgid="4986518933445178928">"Закрыць"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Адкрыць меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Размясціць на палавіне экрана"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Немагчыма змяніць памер праграмы"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Нельга перамясціць сюды праграму"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Разгарнуць"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Размясціць злева"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Размясціць справа"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index b385d285350b..8a69176f2b6f 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблеми с камерата?\nДокоснете за ремонтиране"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблемът не се отстрани?\nДокоснете за връщане в предишното състояние"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нямате проблеми с камерата? Докоснете, за да отхвърлите."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Докоснете, за да отворите менюто на приложението"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Докоснете, за да видите няколко приложения заедно"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Връщане към цял екран от менюто на приложението"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Преглеждайте и правете повече неща"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Преместете друго приложение с плъзгане, за да преминете в режим за разделен екран"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Управление на прозорците"</string> <string name="close_text" msgid="4986518933445178928">"Затваряне"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отваряне на менюто"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Прилепване на екрана"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Това приложение не може да бъде преоразмерено"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложението не може да бъде преместено тук"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Увеличаване"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Прилепване наляво"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Прилепване надясно"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index 56fcf7f4fff5..3799c9fd1dca 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ক্যামেরা সংক্রান্ত সমস্যা?\nরিফিট করতে ট্যাপ করুন"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"এখনও সমাধান হয়নি?\nরিভার্ট করার জন্য ট্যাপ করুন"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ক্যামেরা সংক্রান্ত সমস্যা নেই? বাতিল করতে ট্যাপ করুন।"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"অ্যাপ মেনু খুলতে ট্যাপ করুন"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"একাধিক অ্যাপ একসাথে দেখতে ট্যাপ করুন"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"অ্যাপ মেনু থেকে ফুল-স্ক্রিন মোডে ফিরে যান"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"দেখুন ও আরও অনেক কিছু করুন"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"স্প্লিট স্ক্রিনের ক্ষেত্রে অন্য কোনও অ্যাপ টেনে আনুন"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"উইন্ডো ম্যানেজ করুন"</string> <string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খুলুন"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্রিনে অ্যাপ মানানসই হিসেবে ছোট বড় করুন"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"এই অ্যাপ ছোট বড় করা যাবে না"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"অ্যাপটি এখানে সরানো যাবে না"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"বড় করুন"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"বাঁদিকে স্ন্যাপ করুন"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ডানদিকে স্ন্যাপ করুন"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index 95362bbfeab5..f0d172ad20d0 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s kamerom?\nDodirnite da ponovo namjestite"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nije popravljeno?\nDodirnite da vratite"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nema problema s kamerom? Dodirnite da odbacite."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Otvaranje menija aplikacije dodirom"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Istovremeni prikaz više aplikacija dodirom"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Povratak na prikaz preko cijelog ekrana putem menija aplikacije"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Pogledajte i učinite više"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Prevucite još jednu aplikaciju za podijeljeni ekran"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje prozorima"</string> <string name="close_text" msgid="4986518933445178928">"Zatvaranje"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje menija"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snimi ekran"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Nije moguće promijeniti veličinu aplikacije"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziranje"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pomicanje ulijevo"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Pomicanje udesno"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index e0b25389e6f1..bf35c90f89ab 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tens problemes amb la càmera?\nToca per resoldre\'ls"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"El problema no s\'ha resolt?\nToca per desfer els canvis"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No tens cap problema amb la càmera? Toca per ignorar."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Toca per obrir el menú de l\'aplicació"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Toca per mostrar diverses aplicacions alhora"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Torna a la pantalla completa des del menú de l\'aplicació"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta i fes més coses"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrossega una altra aplicació per utilitzar la pantalla dividida"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gestiona les finestres"</string> <string name="close_text" msgid="4986518933445178928">"Tanca"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Obre el menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajusta la pantalla"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"No es pot canviar la mida d\'aquesta aplicació"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"L\'aplicació no es pot moure aquí"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximitza"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajusta a l\'esquerra"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Ajusta a la dreta"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index 3aae52e4808a..7fc1033b71be 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s fotoaparátem?\nKlepnutím vyřešíte"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepomohlo to?\nKlepnutím se vrátíte"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Žádné problémy s fotoaparátem? Klepnutím zavřete."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Klepnutím otevřete nabídku aplikace"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Klepnutím zobrazíte několik aplikací najednou"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Návrat na celou obrazovku z nabídky aplikace"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lepší zobrazení a více možností"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Přetáhnutím druhé aplikace použijete rozdělenou obrazovku"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Spravovat okna"</string> <string name="close_text" msgid="4986518933445178928">"Zavřít"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otevřít nabídku"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Rozpůlit obrazovku"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Velikost aplikace nelze změnit"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovat"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Přichytit vlevo"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Přichytit vpravo"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index fdc59137dd5e..717e6c42dd51 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du problemer med dit kamera?\nTryk for at gendanne det oprindelige format"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Løste det ikke problemet?\nTryk for at fortryde"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen problemer med dit kamera? Tryk for at afvise."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tryk for at åbne appmenuen"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tryk for at se flere apps på én gang"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Gå tilbage til fuld skærm via appmenuen"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gør mere"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Træk en anden app hertil for at bruge opdelt skærm"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Administrer vinduer"</string> <string name="close_text" msgid="4986518933445178928">"Luk"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åbn menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tilpas skærm"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Størrelsen på denne app kan ikke justeres"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apps kan ikke flyttes hertil"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimér"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fastgør til venstre"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Fastgør til højre"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 913e3d0abf0f..bccd4ae1d6df 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Probleme mit der Kamera?\nZum Anpassen tippen."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Das Problem ist nicht behoben?\nZum Rückgängigmachen tippen."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Keine Probleme mit der Kamera? Zum Schließen tippen."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Zum Öffnen des App-Menüs tippen"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tippen, um mehrere Apps gleichzeitig anzuzeigen"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Über das App-Menü zum Vollbildmodus zurückkehren"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Mehr sehen und erledigen"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Für Splitscreen-Modus weitere App hineinziehen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Fenster verwalten"</string> <string name="close_text" msgid="4986518933445178928">"Schließen"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü öffnen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Bildschirm teilen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Die Größe dieser App kann nicht geändert werden"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Die App kann nicht hierher verschoben werden"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximieren"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Links andocken"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Rechts andocken"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 564fa910fdcd..1039273978e1 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Προβλήματα με την κάμερα;\nΠατήστε για επιδιόρθωση."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Δεν διορθώθηκε;\nΠατήστε για επαναφορά."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Δεν αντιμετωπίζετε προβλήματα με την κάμερα; Πατήστε για παράβλεψη."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Πατήστε για άνοιγμα του μενού της εφαρμογής"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Πατήστε για εμφάνιση πολλών εφαρμογών μαζί"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Επιστρέψτε στην πλήρη οθόνη από το μενού της εφαρμογής"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Δείτε και κάντε περισσότερα"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Σύρετε σε μια άλλη εφαρμογή για διαχωρισμό οθόνης."</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Διαχείριση παραθύρων"</string> <string name="close_text" msgid="4986518933445178928">"Κλείσιμο"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Άνοιγμα μενού"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Προβολή στο μισό της οθόνης"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Δεν είναι δυνατή η αλλαγή μεγέθους αυτής της εφαρμογής"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Μεγιστοποίηση"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Κούμπωμα αριστερά"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Κούμπωμα δεξιά"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index e4ece9386a3f..98da627a4434 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tap to open the app menu"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tap to show multiple apps together"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Return to fullscreen from the app menu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index 14fe60b3e3cb..e928fe02fbcf 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tap to open the app menu"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tap to show multiple apps together"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Return to fullscreen from the app menu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Manage Windows"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap Screen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximize"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index e4ece9386a3f..98da627a4434 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tap to open the app menu"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tap to show multiple apps together"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Return to fullscreen from the app menu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index e4ece9386a3f..98da627a4434 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tap to open the app menu"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tap to show multiple apps together"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Return to fullscreen from the app menu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index dda97abf7b03..e48a9dbc2ebb 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Camera issues?\nTap to refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Didn’t fix it?\nTap to revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"No camera issues? Tap to dismiss."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tap to open the app menu"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tap to show multiple apps together"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Return to fullscreen from the app menu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"See and do more"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Drag in another app for split screen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Manage Windows"</string> <string name="close_text" msgid="4986518933445178928">"Close"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap Screen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximize"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index a4c03632cbe4..f349cbb1aeed 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Tienes problemas con la cámara?\nPresiona para reajustarla"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se resolvió?\nPresiona para revertir los cambios"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No tienes problemas con la cámara? Presionar para descartar."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Presiona para abrir el menú de la app"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Presiona para mostrar varias apps juntas"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Regresa a pantalla completa desde el menú de la app"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Aprovecha más"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra app para el modo de pantalla dividida"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Administrar ventanas"</string> <string name="close_text" msgid="4986518933445178928">"Cerrar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir el menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"No se puede cambiar el tamaño de esta app"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"No se puede mover la app aquí"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar a la izquierda"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Ajustar a la derecha"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 69f7d7b020d6..9f6b2e65c27c 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"¿Problemas con la cámara?\nToca para reajustar"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"¿No se ha solucionado?\nToca para revertir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"¿No hay problemas con la cámara? Toca para cerrar."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Toca para abrir el menú de aplicaciones"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Toca para mostrar varias aplicaciones a la vez"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Vuelve a pantalla completa desde el menú de aplicaciones"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Consulta más información y haz más"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra otra aplicación para activar la pantalla dividida"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gestionar ventanas"</string> <string name="close_text" msgid="4986518933445178928">"Cerrar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar pantalla"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"No se puede cambiar el tamaño de esta aplicación"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"La aplicación no se puede mover aquí"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Acoplar a la izquierda"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Acoplar a la derecha"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index 3d929f6cd8d3..b2b06d6db5d2 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kas teil on kaameraprobleeme?\nPuudutage ümberpaigutamiseks."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Kas probleemi ei lahendatud?\nPuudutage ennistamiseks."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kas kaameraprobleeme pole? Puudutage loobumiseks."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Puudutage rakenduse menüü avamiseks"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Puudutage mitme rakenduse koos kuvamiseks"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Minge rakenduse menüüst tagasi täisekraanile"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vaadake ja tehke rohkem"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lohistage muusse rakendusse, et jagatud ekraanikuva kasutada"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Akende haldamine"</string> <string name="close_text" msgid="4986518933445178928">"Sule"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ava menüü"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Kuva poolel ekraanil"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Selle rakenduse aknasuurust ei saa muuta"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Rakendust ei saa siia teisaldada"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimeeri"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Tõmmake vasakule"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Tõmmake paremale"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 39bcf080fcf3..4a71c49b8d8a 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Arazoak dauzkazu kamerarekin?\nBerriro doitzeko, sakatu hau."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ez al da konpondu?\nLeheneratzeko, sakatu hau."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ez daukazu arazorik kamerarekin? Baztertzeko, sakatu hau."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Sakatu hau aplikazioen menua irekitzeko"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Sakatu hau aplikazio bat baino gehiago aldi berean erakusteko"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Itzuli pantaila osora aplikazioen menutik"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ikusi eta egin gauza gehiago"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Pantaila zatitua ikusteko, arrastatu beste aplikazio bat"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Kudeatu leihoak"</string> <string name="close_text" msgid="4986518933445178928">"Itxi"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ireki menua"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zatitu pantaila"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ezin zaio aldatu tamaina aplikazio honi"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikazioa ezin da hona ekarri"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizatu"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ezarri ezkerrean"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Ezarri eskuinean"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 9f607bf89656..941ff84b7799 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه تکضرب بزنید"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن تکضرب بزنید"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن تکضرب بزنید."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"برای باز کردن منو برنامه، تکضرب بزنید"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"برای نمایش چند برنامه با هم، تکضرب بزنید"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"از منو برنامه به تمامصفحه برگردید"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه بهطور همزمان استفاده کنید"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامهای دیگر بکشید"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دو تکضرب بزنید"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"مدیریت کردن پنجرهها"</string> <string name="close_text" msgid="4986518933445178928">"بستن"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"باز کردن منو"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"بزرگ کردن صفحه"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"اندازه این برنامه را نمیتوان تغییر داد"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"برنامه را نمیتوان به اینجا منتقل کرد"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"بزرگ کردن"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"کشیدن بهچپ"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"کشیدن بهراست"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml index 55394cbdc31a..7b0c8c6c923d 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml @@ -18,7 +18,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر در تصویر"</string> - <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string> + <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بیعنوان)"</string> <string name="pip_close" msgid="2955969519031223530">"بستن"</string> <string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string> <string name="pip_move" msgid="158770205886688553">"انتقال"</string> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index 04b6241ca90b..a45e9afeabfc 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Onko kameran kanssa ongelmia?\nKorjaa napauttamalla"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Eikö ongelma ratkennut?\nKumoa napauttamalla"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ei ongelmia kameran kanssa? Hylkää napauttamalla."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Avaa sovellusvalikko napauttamalla"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Näytä useita sovelluksia yhdessä napauttamalla"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Palaa koko näytön tilaan sovellusvalikosta"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Näe ja tee enemmän"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Käytä jaettua näyttöä vetämällä tähän toinen sovellus"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Hallinnoi ikkunoita"</string> <string name="close_text" msgid="4986518933445178928">"Sulje"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Avaa valikko"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Suurenna näyttö"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Jaa näyttö"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Tämän sovellusikkunan kokoa ei voi muuttaa"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Sovellusta ei voi siirtää tänne"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Suurenna"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Siirrä vasemmalle"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Siirrä oikealle"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 73129525ebdd..c163165a8296 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Toucher ici pour ouvrir le menu de l\'appli"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Toucher ici pour afficher plusieurs applis ensemble"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Revenir au mode Plein écran à partir du menu de l\'appli"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une appli pour la repositionner"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gérer les fenêtres"</string> <string name="close_text" msgid="4986518933445178928">"Fermer"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aligner l\'écran"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Impossible de redimensionner cette appli"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Agrandir"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Épingler à gauche"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Épingler à droite"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index 8f4e58f99a13..b2a6f16733b3 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo ?\nAppuyez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu ?\nAppuyez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo ? Appuyez pour ignorer."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Appuyer pour ouvrir le menu de l\'appli"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Appuyer pour afficher plusieurs applis simultanément"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Revenir en plein écran depuis le menu de l\'appli"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et interagir plus"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gérer les fenêtres"</string> <string name="close_text" msgid="4986518933445178928">"Fermer"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fractionner l\'écran"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Impossible de redimensionner cette appli"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Agrandir"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ancrer à gauche"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Ancrer à droite"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index 5c0aa074b329..ab972f94af22 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Tes problemas coa cámara?\nToca para reaxustala"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Non se solucionaron os problemas?\nToca para reverter o seu tratamento"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Non hai problemas coa cámara? Tocar para ignorar."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Toca para abrir o menú da aplicación"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Toca para mostrar varias aplicacións xuntas"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Volve á pantalla completa desde o menú da aplicación"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Ver e facer máis"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arrastra outra aplicación para usar a pantalla dividida"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Xestionar as ventás"</string> <string name="close_text" msgid="4986518933445178928">"Pechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar pantalla"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Non se pode cambiar o tamaño desta aplicación"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Non se pode mover aquí a aplicación"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Axustar á esquerda"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Axustar á dereita"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index a382d0b41652..0dc2f73f1134 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"કૅમેરામાં સમસ્યાઓ છે?\nફરીથી ફિટ કરવા માટે ટૅપ કરો"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"સુધારો નથી થયો?\nપહેલાંના પર પાછું ફેરવવા માટે ટૅપ કરો"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"કૅમેરામાં કોઈ સમસ્યા નથી? છોડી દેવા માટે ટૅપ કરો."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ઍપ મેનૂ ખોલવા માટે ટૅપ કરો"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"એકથી વધુ ઍપ એકસાથે બતાવવા માટે ટૅપ કરો"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ઍપ મેનૂમાંથી પૂર્ણસ્ક્રીન પર પાછા ફરો"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"જુઓ અને બીજું ઘણું કરો"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"વિભાજિત સ્ક્રીન માટે કોઈ અન્ય ઍપમાં ખેંચો"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"વિન્ડો મેનેજ કરો"</string> <string name="close_text" msgid="4986518933445178928">"બંધ કરો"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"મેનૂ ખોલો"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"સ્ક્રીન સ્નૅપ કરો"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"આ ઍપના કદમાં વધઘટ કરી શકાતો નથી"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ઍપ અહીં ખસેડી શકાતી નથી"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"મોટું કરો"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ડાબે સ્નૅપ કરો"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"જમણે સ્નૅપ કરો"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index d6fc85b6cdcc..679d800a4dd2 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्या कैमरे से जुड़ी कोई समस्या है?\nफिर से फ़िट करने के लिए टैप करें"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"क्या समस्या ठीक नहीं हुई?\nपहले जैसा करने के लिए टैप करें"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्या कैमरे से जुड़ी कोई समस्या नहीं है? खारिज करने के लिए टैप करें."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ऐप्लिकेशन मेन्यू खोलने के लिए टैप करें"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"कई ऐप्लिकेशन एक साथ दिखाने के लिए टैप करें"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ऐप्लिकेशन मेन्यू से फ़ुलस्क्रीन मोड पर वापस जाएं"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पूरी जानकारी लेकर, बेहतर तरीके से काम करें"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन का इस्तेमाल करने के लिए, किसी अन्य ऐप्लिकेशन को खींचें और छोड़ें"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"विंडो मैनेज करें"</string> <string name="close_text" msgid="4986518933445178928">"बंद करें"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेन्यू खोलें"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्नैप स्क्रीन"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"इस ऐप्लिकेशन का साइज़ नहीं बदला जा सकता"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"बड़ा करें"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"बाईं ओर स्नैप करें"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"दाईं ओर स्नैप करें"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index f233c029c752..1e5ffc858b86 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi s fotoaparatom?\nDodirnite za popravak"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problem nije riješen?\nDodirnite za vraćanje"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemate problema s fotoaparatom? Dodirnite za odbacivanje."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Dodirnite za otvaranje izbornika aplikacije"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Dodirnite za prikaz više aplikacija zajedno"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Vratite se na cijeli zaslon iz izbornika aplikacije"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Gledajte i učinite više"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Povucite drugu aplikaciju unutra da biste podijelili zaslon"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje prozorima"</string> <string name="close_text" msgid="4986518933445178928">"Zatvorite"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje izbornika"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Izradi snimku zaslona"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Nije moguće promijeniti veličinu aplikacije"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti ovdje"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziraj"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Poravnaj lijevo"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Poravnaj desno"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index aaa4e2654f39..7c90a1924214 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamerával kapcsolatos problémába ütközött?\nKoppintson a megoldáshoz."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nem sikerült a hiba kijavítása?\nKoppintson a visszaállításhoz."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nincsenek problémái kamerával? Koppintson az elvetéshez."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Koppintson az alkalmazásmenü megnyitásához"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Koppintson több alkalmazás együttes megjelenítéséhez"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"A teljes képernyőre az alkalmazásmenüben térhet vissza"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Több mindent láthat és tehet"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Húzzon ide egy másik alkalmazást az osztott képernyő használatához"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Ablakok kezelése"</string> <string name="close_text" msgid="4986518933445178928">"Bezárás"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü megnyitása"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Igazodás a képernyő adott részéhez"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ezt az alkalmazást nem lehet átméretezni"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Az alkalmazás nem helyezhető át ide"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Teljes méret"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Balra igazítás"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Jobbra igazítás"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index cd21faab45c2..e8ed4ac9c9a4 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Տեսախցիկի հետ կապված խնդիրնե՞ր կան։\nՀպեք՝ վերակարգավորելու համար։"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Չհաջողվե՞ց շտկել։\nՀպեք՝ փոփոխությունները չեղարկելու համար։"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Տեսախցիկի հետ կապված խնդիրներ չկա՞ն։ Փակելու համար հպեք։"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Հպեք՝ հավելվածի ընտրացանկը բացելու համար"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Հպեք՝ էկրանին մի քանի հավելված միասին դիտելու համար"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Հավելվածի ընտրացանկից վերադառնալ լիաէկրան ռեժիմ"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Միաժամանակ կատարեք մի քանի առաջադրանք"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Քաշեք մյուս հավելվածի մեջ՝ էկրանի տրոհումն օգտագործելու համար"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Կառավարել պատուհանները"</string> <string name="close_text" msgid="4986518933445178928">"Փակել"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Բացել ընտրացանկը"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ծալել էկրանը"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Այս հավելվածի չափը հնարավոր չէ փոխել"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ծավալել"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ամրացնել ձախ կողմում"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Ամրացնել աջ կողմում"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index ba0683deecd8..06b1634917e9 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Masalah kamera?\nKetuk untuk memperbaiki"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tidak dapat diperbaiki?\nKetuk untuk mengembalikan"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tidak ada masalah kamera? Ketuk untuk menutup."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Ketuk untuk membuka menu aplikasi"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Ketuk untuk menampilkan beberapa aplikasi secara bersamaan"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Kembali ke layar penuh dari menu aplikasi"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih banyak hal"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Tarik aplikasi lain untuk menggunakan layar terpisah"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Kelola Jendela"</string> <string name="close_text" msgid="4986518933445178928">"Tutup"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Gabungkan Layar"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ukuran aplikasi ini tidak dapat diubah"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikasi tidak dapat dipindahkan ke sini"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimalkan"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Maksimalkan ke kiri"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Maksimalkan ke kanan"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index b427eaf23290..2d447771de25 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Myndavélavesen?\nÝttu til að breyta stærð"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ennþá vesen?\nÝttu til að afturkalla"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Ekkert myndavélavesen? Ýttu til að hunsa."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Ýttu til að opna forritavalmyndina"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Ýttu til að sjá mörg forrit saman"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Opnaðu allan skjáinn aftur á forritavalmyndinni"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sjáðu og gerðu meira"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dragðu annað forrit inn til að nota skjáskiptingu"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Stjórna gluggum"</string> <string name="close_text" msgid="4986518933445178928">"Loka"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Opna valmynd"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Smelluskjár"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ekki er hægt að breyta stærð þessa forrits"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Stækka"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Smella til vinstri"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Smella til hægri"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 9eb9d4e3a403..b56424594f29 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemi con la fotocamera?\nTocca per risolverli"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Il problema non si è risolto?\nTocca per ripristinare"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nessun problema con la fotocamera? Tocca per ignorare."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tocca per aprire il menu dell\'app"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tocca per mostrare più app insieme"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Torna allo schermo intero dal menu dell\'app"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Visualizza più contenuti e fai di più"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trascina in un\'altra app per usare lo schermo diviso"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gestisci finestre"</string> <string name="close_text" msgid="4986518933445178928">"Chiudi"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Apri il menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aggancia schermo"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Non è possibile ridimensionare questa app"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ingrandisci"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Aggancia a sinistra"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Aggancia a destra"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 3e82198201b9..fa072639f089 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר להקיש כדי לבצע התאמה מחדש"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי לחזור לגרסה הקודמת"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי לסגור."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"צריך להקיש כדי לפתוח את תפריט האפליקציה"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"אפשר להקיש כדי להציג כמה אפליקציות יחד"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"חזרה למסך מלא מתפריט האפליקציה"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך המפוצל"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ניהול החלונות"</string> <string name="close_text" msgid="4986518933445178928">"סגירה"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"פתיחת התפריט"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"כיווץ המסך"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"לא ניתן לשנות את גודל החלון של האפליקציה הזו"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"לא ניתן להעביר את האפליקציה לכאן"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"הגדלה"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"הצמדה לשמאל"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"הצמדה לימין"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 5d8dc5e85462..91667c00ce12 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"カメラに関する問題の場合は、\nタップすると修正できます"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"修正されなかった場合は、\nタップすると元に戻ります"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"カメラに関する問題でない場合は、タップすると閉じます。"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"タップするとアプリメニューが開きます"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"タップすると複数のアプリが同時に表示されます"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"アプリメニューから全画面表示に戻ります"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"表示を拡大して機能を強化"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"分割画面にするにはもう 1 つのアプリをドラッグしてください"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ウィンドウを管理する"</string> <string name="close_text" msgid="4986518933445178928">"閉じる"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"メニューを開く"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"画面のスナップ"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"このアプリはサイズ変更できません"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"アプリはここに移動できません"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"左にスナップ"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"右にスナップ"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index de08b9ca2d7e..2208348485af 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"კამერად პრობლემები აქვს?\nშეეხეთ გამოსასწორებლად"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"არ გამოსწორდა?\nშეეხეთ წინა ვერსიის დასაბრუნებლად"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"კამერას პრობლემები არ აქვს? შეეხეთ უარყოფისთვის."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"შეეხეთ აპის მენიუს გასახსნელად"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"შეეხეთ რამდენიმე აპის ერთად საჩვენებლად"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"სრულეკრანიან რეჟიმზე დაბრუნდით აპის მენიუდან"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"მეტის ნახვა და გაკეთება"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ეკრანის გასაყოფად ჩავლებით გადაიტანეთ სხვა აპში"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ფანჯრების მართვა"</string> <string name="close_text" msgid="4986518933445178928">"დახურვა"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"მენიუს გახსნა"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"აპლიკაციის დაპატარავება ეკრანზე"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"აპის ზომის შეცვლა შეუძლებელია"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"აპის აქ გადატანა შეუძლებელია"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"მაქსიმალურად გაშლა"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"მარცხნივ გადატანა"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"მარჯვნივ გადატანა"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index dd4e5c94fc27..416a84c86281 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада қателер шықты ма?\nЖөндеу үшін түртіңіз."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Жөнделмеді ме?\nҚайтару үшін түртіңіз."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада қателер шықпады ма? Жабу үшін түртіңіз."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Қолданба мәзірін ашу үшін түртіңіз"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Бірнеше қолданбаны қатар көрсету үшін түртіңіз"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Қолданба мәзірінен толық экран режиміне қайту"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Қосымша ақпаратты қарап, әрекеттер жасау"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлу үшін басқа қолданбаға өтіңіз."</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Терезелерді басқару"</string> <string name="close_text" msgid="4986518933445178928">"Жабу"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Мәзірді ашу"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды бөлу"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Бұл қолданбаның өлшемі өзгертілмейді."</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Қолданба бұл жерге қойылмайды."</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Жаю"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Солға тіркеу"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Оңға тіркеу"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 96fe62e4f034..b074d65700f4 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"មានបញ្ហាពាក់ព័ន្ធនឹងកាមេរ៉ាឬ?\nចុចដើម្បីដោះស្រាយ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"មិនបានដោះស្រាយបញ្ហានេះទេឬ?\nចុចដើម្បីត្រឡប់"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"មិនមានបញ្ហាពាក់ព័ន្ធនឹងកាមេរ៉ាទេឬ? ចុចដើម្បីច្រានចោល។"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ចុចដើម្បីបើកម៉ឺនុយកម្មវិធី"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"ចុចដើម្បីបង្ហាញកម្មវិធីច្រើនរួមគ្នា"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ត្រឡប់ទៅអេក្រង់ពេញវិញពីម៉ឺនុយកម្មវិធី"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"មើលឃើញ និងធ្វើបានកាន់តែច្រើន"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"អូសកម្មវិធីមួយទៀតចូល ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដងនៅក្រៅកម្មវិធី ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"គ្រប់គ្រងវិនដូ"</string> <string name="close_text" msgid="4986518933445178928">"បិទ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"បិទម៉ឺនុយ"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"បើកម៉ឺនុយ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ថតអេក្រង់"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"មិនអាចប្ដូរទំហំកម្មវិធីនេះបានទេ"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ពង្រីក"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ផ្លាស់ទីទៅឆ្វេង"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ផ្លាស់ទីទៅស្ដាំ"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index b38f74411db2..9c22241f5037 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿವೆಯೇ?\nಮರುಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ಅದನ್ನು ಸರಿಪಡಿಸಲಿಲ್ಲವೇ?\nಹಿಂತಿರುಗಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ಕ್ಯಾಮರಾ ಸಮಸ್ಯೆಗಳಿಲ್ಲವೇ? ವಜಾಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ಆ್ಯಪ್ ಮೆನುವನ್ನು ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"ಅನೇಕ ಆ್ಯಪ್ಗಳನ್ನು ಒಟ್ಟಿಗೆ ತೋರಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ಆ್ಯಪ್ ಮೆನುವಿನಿಂದ ಫುಲ್ಸ್ಕ್ರೀನ್ಗೆ ಹಿಂತಿರುಗಿ"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ನೋಡಿ ಮತ್ತು ಹೆಚ್ಚಿನದನ್ನು ಮಾಡಿ"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ಗಾಗಿ ಮತ್ತೊಂದು ಆ್ಯಪ್ನಲ್ಲಿ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ವಿಂಡೋಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string> <string name="close_text" msgid="4986518933445178928">"ಮುಚ್ಚಿ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ಮೆನು ತೆರೆಯಿರಿ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ಸ್ನ್ಯಾಪ್ ಸ್ಕ್ರೀನ್"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಮರುಗಾತ್ರಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ಎಡಕ್ಕೆ ಸ್ನ್ಯಾಪ್ ಮಾಡಿ"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ಬಲಕ್ಕೆ ಸ್ನ್ಯಾಪ್ ಮಾಡಿ"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 421d4666911e..58dd6f8e7e99 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"카메라 문제가 있나요?\n해결하려면 탭하세요."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"해결되지 않았나요?\n되돌리려면 탭하세요."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"카메라에 문제가 없나요? 닫으려면 탭하세요."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"탭하여 앱 메뉴 열기"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"탭하여 여러 앱을 함께 표시하기"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"앱 메뉴에서 전체 화면으로 돌아가기"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"더 많은 정보를 보고 더 많은 작업을 처리하세요"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"화면 분할을 사용하려면 다른 앱을 드래그해 가져옵니다."</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"창 관리"</string> <string name="close_text" msgid="4986518933445178928">"닫기"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"메뉴 열기"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"화면 분할"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"이 앱은 크기를 조절할 수 없습니다."</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"앱을 여기로 이동할 수 없음"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"최대화하기"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"왼쪽으로 맞추기"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"오른쪽으로 맞추기"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index abafd7ac0330..75feedea1b89 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерада маселелер келип чыктыбы?\nОңдоо үчүн таптаңыз"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Оңдолгон жокпу?\nАртка кайтаруу үчүн таптаңыз"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерада маселе жокпу? Этибарга албоо үчүн таптаңыз."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Колдонмонун менюсун ачуу үчүн таптап коюңуз"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Бир нече колдонмону чогуу көрүү үчүн таптап коюңуз"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Колдонмонун менюсунан толук экранга кайтыңыз"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Көрүп, көбүрөөк нерселерди жасаңыз"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Экранды бөлүү үчүн башка колдонмону сүйрөңүз"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Терезелерди тескөө"</string> <string name="close_text" msgid="4986518933445178928">"Жабуу"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Менюну ачуу"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Экранды сүрөткө тартып алуу"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Бул колдонмонун өлчөмүн өзгөртүүгө болбойт"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Колдонмону бул жерге жылдырууга болбойт"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Чоңойтуу"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Солго жылдыруу"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Оңго жылдыруу"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 5bc5316494b3..8f28504b41b4 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ?\nແຕະເພື່ອປັບໃໝ່"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ບໍ່ໄດ້ແກ້ໄຂມັນບໍ?\nແຕະເພື່ອແປງກັບຄືນ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ບໍ່ມີບັນຫາກ້ອງຖ່າຍຮູບບໍ? ແຕະເພື່ອປິດໄວ້."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ແຕະເພື່ອເປີດເມນູແອັບ"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"ແຕະເພື່ອສະແດງແອັບຫຼາຍລາຍການພ້ອມກັນ"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ກັບຄືນໄປຫາໂໝດເຕັມຈໍຈາກເມນູແອັບ"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ເບິ່ງ ແລະ ເຮັດຫຼາຍຂຶ້ນ"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ລາກໄປໄວ້ໃນແອັບອື່ນເພື່ອແບ່ງໜ້າຈໍ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ຈັດການໜ້າຈໍ"</string> <string name="close_text" msgid="4986518933445178928">"ປິດ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ເປີດເມນູ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ສະແນັບໜ້າຈໍ"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ບໍ່ສາມາດປັບຂະໜາດແອັບນີ້ໄດ້"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ແນບຊ້າຍ"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ແນບຂວາ"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index 01a72ee4cced..b97b8787ed10 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Iškilo problemų dėl kameros?\nPalieskite, kad pritaikytumėte iš naujo"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nepavyko pataisyti?\nPalieskite, kad grąžintumėte"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nėra jokių problemų dėl kameros? Palieskite, kad atsisakytumėte."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Palieskite, kad atidarytumėte programos meniu"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Palieskite, kad būtų rodomos kelios programos kartu"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Grįžkite į viso ekrano režimą iš programos meniu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daugiau turinio ir funkcijų"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Vilkite kitoje programoje, kad galėtumėte naudoti išskaidyto ekrano režimą"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Tvarkyti langus"</string> <string name="close_text" msgid="4986518933445178928">"Uždaryti"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atidaryti meniu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Sutraukti ekraną"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Negalima keisti šios programos dydžio"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Padidinti"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pritraukti kairėje"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Pritraukti dešinėje"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 2f235babd721..de200d816e7a 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Vai ir problēmas ar kameru?\nPieskarieties, lai tās novērstu."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Vai problēma netika novērsta?\nPieskarieties, lai atjaunotu."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Vai nav problēmu ar kameru? Pieskarieties, lai nerādītu."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Lai atvērtu lietotnes izvēlni, pieskarieties."</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Lai parādītu vairākas lietotnes kopā, pieskarieties."</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Varat atgriezties pilnekrāna režīmā no lietotnes izvēlnes."</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Uzziniet un paveiciet vairāk"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Lai izmantotu sadalītu ekrānu, ievelciet vēl vienu lietotni"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Pārvaldīt logus"</string> <string name="close_text" msgid="4986518933445178928">"Aizvērt"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atvērt izvēlni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fiksēt ekrānu"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Šīs lietotnes loga lielumu nevar mainīt."</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Lietotni nevar pārvietot šeit."</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimizēt"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Piestiprināt pa kreisi"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Piestiprināt pa labi"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index e58d8fc945f4..4922d042c6ac 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми со камерата?\nДопрете за да се совпадне повторно"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не се поправи?\nДопрете за враќање"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нема проблеми со камерата? Допрете за отфрлање."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Допрете за да го отворите менито со апликации"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Допрете за да се прикажат повеќе апликации заедно"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Вратете се на цел екран од менито со апликации"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Погледнете и направете повеќе"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Повлечете друга апликација за поделен екран"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Управувајте со прозорци"</string> <string name="close_text" msgid="4986518933445178928">"Затворете"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отвори го менито"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Подели го екранот на половина"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Не може да се промени големината на апликацијава"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликацијата не може да се премести овде"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Максимизирај"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Фотографирај лево"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Фотографирај десно"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index d51c3fb70a9e..61277d6765fd 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ക്യാമറ പ്രശ്നങ്ങളുണ്ടോ?\nശരിയാക്കാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"അത് പരിഹരിച്ചില്ലേ?\nപുനഃസ്ഥാപിക്കാൻ ടാപ്പ് ചെയ്യുക"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ക്യാമറാ പ്രശ്നങ്ങളൊന്നുമില്ലേ? നിരസിക്കാൻ ടാപ്പ് ചെയ്യുക."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ആപ്പ് മെനു തുറക്കാൻ ടാപ്പ് ചെയ്യുക"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"ഒന്നിലധികം ആപ്പുകൾ ഒരുമിച്ച് കാണിക്കാൻ ടാപ്പ് ചെയ്യുക"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ആപ്പ് മെനുവിൽ നിന്ന് പൂർണ്ണസ്ക്രീനിലേക്ക് മടങ്ങുക"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"കൂടുതൽ കാണുക, ചെയ്യുക"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"സ്ക്രീൻ വിഭജന മോഡിന്, മറ്റൊരു ആപ്പ് വലിച്ചിടുക"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"വിൻഡോകൾ മാനേജ് ചെയ്യുക"</string> <string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"മെനു തുറക്കുക"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്ക്രീൻ വലുതാക്കുക"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"സ്ക്രീൻ സ്നാപ്പ് ചെയ്യുക"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ഈ ആപ്പിന്റെ വലുപ്പം മാറ്റാനാകില്ല"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"വലുതാക്കുക"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ഇടതുവശത്തേക്ക് സ്നാപ്പ് ചെയ്യുക"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"വലതുവശത്തേക്ക് സ്നാപ്പ് ചെയ്യുക"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index f7e6a6c87890..2b313a2be17c 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Камерын асуудал гарсан уу?\nДахин тааруулахын тулд товшино уу"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Үүнийг засаагүй юу?\nБуцаахын тулд товшино уу"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Камерын асуудал байхгүй юу? Хаахын тулд товшино уу."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Аппын цэсийг нээхийн тулд товшино уу"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Олон аппыг хамтад нь харуулахын товшино уу"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Аппын цэсээс бүтэн дэлгэц рүү буцна уу"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Харж илүү ихийг хий"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Дэлгэц хуваах горимд ашиглахын тулд өөр аппыг чирнэ үү"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Windows-г удирдах"</string> <string name="close_text" msgid="4986518933445178928">"Хаах"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Цэсийг нээх"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Дэлгэцийг таллах"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Энэ аппын хэмжээг өөрчлөх боломжгүй"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Аппыг ийш зөөх боломжгүй"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Томруулах"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Зүүн тийш зэрэгцүүлэх"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Баруун тийш зэрэгцүүлэх"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 483228424d44..9778dcd0a166 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"कॅमेराशी संबंधित काही समस्या आहेत का?\nपुन्हा फिट करण्यासाठी टॅप करा"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"निराकरण झाले नाही?\nरिव्हर्ट करण्यासाठी कृपया टॅप करा"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"कॅमेराशी संबंधित कोणत्याही समस्या नाहीत का? डिसमिस करण्यासाठी टॅप करा."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"अॅप मेनू उघडण्यासाठी टॅप करा"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"एकाहून अधिक ॲप्स एकत्र दाखवण्यासाठी टॅप करा"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ॲप मेनूमधून फुलस्क्रीनवर परत या"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"पहा आणि आणखी बरेच काही करा"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप ड्रॅग करा"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"विंडो व्यवस्थापित करा"</string> <string name="close_text" msgid="4986518933445178928">"बंद करा"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनू उघडा"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्क्रीन स्नॅप करा"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"या अॅपचा आकार बदलला जाऊ शकत नाही"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"अॅप इथे हलवू शकत नाही"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"मोठे करा"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"डावीकडे स्नॅप करा"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"उजवीकडे स्नॅप करा"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index e510d723f1e5..0ee439657e2a 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Isu kamera?\nKetik untuk memuatkan semula"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Isu tidak dibetulkan?\nKetik untuk kembali"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Tiada isu kamera? Ketik untuk mengetepikan."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Ketik untuk membuka menu apl"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Ketik untuk memaparkan berbilang apl serentak"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Kembali kepada skrin penuh daripada menu apl"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Lihat dan lakukan lebih"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Seret masuk apl lain untuk menggunakan skrin pisah"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Urus Tetingkap"</string> <string name="close_text" msgid="4986518933445178928">"Tutup"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tangkap Skrin"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Apl ini tidak boleh diubah saiz"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimumkan"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Autojajar ke kiri"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Autojajar ke kanan"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index aac6f8420d7a..57d09500b3bf 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ကင်မရာပြဿနာလား။\nပြင်ဆင်ရန် တို့ပါ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ကောင်းမသွားဘူးလား။\nပြန်ပြောင်းရန် တို့ပါ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ကင်မရာပြဿနာ မရှိဘူးလား။ ပယ်ရန် တို့ပါ။"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"အက်ပ်မီနူးကိုဖွင့်ရန် တို့ပါ"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"အက်ပ်များစွာကို အတူတကွပြရန် တို့ပါ"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"အက်ပ်မီနူးမှ ဖန်သားပြင်အပြည့်သို့ ပြန်သွားပါ"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းအတွက် အက်ပ်နောက်တစ်ခုကို ဖိဆွဲပါ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ဝင်းဒိုးများ စီမံရန်"</string> <string name="close_text" msgid="4986518933445178928">"ပိတ်ရန်"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"မီနူး ဖွင့်ရန်"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"စခရင်ကို ချုံ့မည်"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ဤအက်ပ်ကို အရွယ်ပြင်၍ မရပါ"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ချဲ့ရန်"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ဘယ်တွင် ချဲ့ရန်"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ညာတွင် ချဲ့ရန်"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 175133d1744a..eb505ec43a65 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Har du kameraproblemer?\nTrykk for å tilpasse"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Ble ikke problemet løst?\nTrykk for å gå tilbake"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Har du ingen kameraproblemer? Trykk for å lukke."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Trykk for å åpne appmenyen"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Trykk for å vise flere apper sammen"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Gå tilbake til fullskjerm fra appmenyen"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se og gjør mer"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra inn en annen app for å bruke delt skjerm"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Administrer vinduene"</string> <string name="close_text" msgid="4986518933445178928">"Lukk"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åpne menyen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fest skjermen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Du kan ikke endre størrelse på denne appen"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Appen kan ikke flyttes hit"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimer"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fest til venstre"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Fest til høyre"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index d3a7e12c0df4..47d66d5deec8 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"क्यामेरासम्बन्धी समस्या देखियो?\nसमस्या हल गर्न ट्याप गर्नुहोस्"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"समस्या हल भएन?\nपहिलेको जस्तै बनाउन ट्याप गर्नुहोस्"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"क्यामेरासम्बन्धी कुनै पनि समस्या छैन? खारेज गर्न ट्याप गर्नुहोस्।"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"एपको मेनु खोल्न ट्याप गर्नुहोस्"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"एकभन्दा बढी एपहरू सँगै देखाउन ट्याप गर्नुहोस्"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"एपको मेनुबाट फुल स्क्रिनमा फर्कनुहोस्"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"थप कुरा हेर्नुहोस् र गर्नुहोस्"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"स्प्लिट स्क्रिन मोड प्रयोग गर्न अर्को एप ड्रयाग एन्ड ड्रप गर्नुहोस्"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"विन्डोहरू व्यवस्थापन गर्नुहोस्"</string> <string name="close_text" msgid="4986518933445178928">"बन्द गर्नुहोस्"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनु खोल्नुहोस्"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रिन ठुलो बनाउनुहोस्"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्क्रिन स्न्याप गर्नुहोस्"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"यो एपको आकार बदल्न मिल्दैन"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"एप सारेर यहाँ ल्याउन सकिएन"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ठुलो बनाउनुहोस्"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"बायाँतिर स्न्याप गर्नुहोस्"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"दायाँतिर स्न्याप गर्नुहोस्"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 747afe3eb034..e3e344124e0b 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Cameraproblemen?\nTik om opnieuw passend te maken."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Is dit geen oplossing?\nTik om terug te zetten."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Geen cameraproblemen? Tik om te sluiten."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tik om het app-menu te openen"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tik om meerdere apps tegelijk te tonen"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Terug naar volledig scherm vanuit het app-menu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zie en doe meer"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep een andere app hier naartoe om het scherm te splitsen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Vensters beheren"</string> <string name="close_text" msgid="4986518933445178928">"Sluiten"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menu openen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Scherm halveren"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Het formaat van deze app kan niet worden aangepast"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Kan de app niet hierheen verplaatsen"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximaliseren"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Links uitlijnen"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Rechts uitlijnen"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index a5adcbe36db1..baf009e8f28c 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"କ୍ୟାମେରାରେ ସମସ୍ୟା ଅଛି?\nପୁଣି ଫିଟ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ଏହାର ସମାଧାନ ହୋଇନାହିଁ?\nଫେରିଯିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"କ୍ୟାମେରାରେ କିଛି ସମସ୍ୟା ନାହିଁ? ଖାରଜ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ଆପ ମେନୁ ଖୋଲିବାକୁ ଟାପ କରନ୍ତୁ"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"ଏକାଠି ଏକାଧିକ ଆପ୍ସ ଦେଖାଇବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ଆପ ମେନୁରୁ ପୂର୍ଣ୍ଣସ୍କ୍ରିନକୁ ଫେରନ୍ତୁ"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ଦେଖନ୍ତୁ ଏବଂ ଆହୁରି ଅନେକ କିଛି କରନ୍ତୁ"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ପାଇଁ ଅନ୍ୟ ଏକ ଆପକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ୱିଣ୍ଡୋଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ"</string> <string name="close_text" msgid="4986518933445178928">"ବନ୍ଦ କରନ୍ତୁ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ମେନୁ ଖୋଲନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ସ୍କ୍ରିନକୁ ସ୍ନାପ କରନ୍ତୁ"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ଏହି ଆପକୁ ରିସାଇଜ କରାଯାଇପାରିବ ନାହିଁ"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ବଡ଼ କରନ୍ତୁ"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ବାମରେ ସ୍ନାପ କରନ୍ତୁ"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ଡାହାଣରେ ସ୍ନାପ କରନ୍ତୁ"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index 5a4e61a2303b..2c29c7f80411 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ?\nਮੁੜ-ਫਿੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"ਕੀ ਇਹ ਠੀਕ ਨਹੀਂ ਹੋਈ?\nਵਾਪਸ ਉਹੀ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"ਕੀ ਕੈਮਰੇ ਸੰਬੰਧੀ ਕੋਈ ਸਮੱਸਿਆ ਨਹੀਂ ਹੈ? ਖਾਰਜ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ਐਪ ਮੀਨੂ ਨੂੰ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"ਕਈ ਐਪਾਂ ਇਕੱਠੀਆਂ ਦਿਖਾਉਣ ਲਈ ਟੈਪ ਕਰੋ"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ਐਪ ਮੀਨੂ ਤੋਂ ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਵਾਪਸ ਜਾਓ"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ਦੇਖੋ ਅਤੇ ਹੋਰ ਬਹੁਤ ਕੁਝ ਕਰੋ"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੇ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਵਿੱਚ ਘਸੀਟੋ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"ਵਿੰਡੋਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> <string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ਸਕ੍ਰੀਨ ਨੂੰ ਸਨੈਪ ਕਰੋ"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ਇਸ ਐਪ ਦਾ ਆਕਾਰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ਵੱਡਾ ਕਰੋ"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ਖੱਬੇ ਪਾਸੇ ਸਨੈਪ ਕਰੋ"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ਸੱਜੇ ਪਾਸੇ ਸਨੈਪ ਕਰੋ"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index a14bb99286f8..1e7b18117812 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemy z aparatem?\nKliknij, aby dopasować"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Naprawa się nie udała?\nKliknij, aby cofnąć"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Brak problemów z aparatem? Kliknij, aby zamknąć"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Kliknij, aby otworzyć menu aplikacji"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Kliknij, aby wyświetlić jednocześnie kilka aplikacji"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Wróć do trybu pełnoekranowego z menu aplikacji"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobacz i zrób więcej"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Aby podzielić ekran, przeciągnij drugą aplikację"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Zarządzaj oknami"</string> <string name="close_text" msgid="4986518933445178928">"Zamknij"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otwórz menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Przyciągnij ekran"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Nie można zmienić rozmiaru tej aplikacji"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksymalizuj"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Przyciągnij do lewej"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Przyciągnij do prawej"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 75c445c6f35c..7d728a03b4ec 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Toque para abrir o menu do app"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Toque para mostrar vários apps juntos"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Volte para a tela cheia no menu do app"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gerenciar janelas"</string> <string name="close_text" msgid="4986518933445178928">"Fechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Não é possível redimensionar o app"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar à esquerda"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Ajustar à direita"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index 1d31d0d92309..752fd6fb8970 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmara?\nToque aqui para reajustar"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nenhum problema com a câmara? Toque para ignorar."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Toque para abrir o menu de apps"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Toque para mostrar várias apps em conjunto"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Regresse ao ecrã inteiro a partir do menu de apps"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outra app para usar o ecrã dividido"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Faça a gestão das janelas"</string> <string name="close_text" msgid="4986518933445178928">"Fechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar ecrã"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Não é possível redimensionar esta app"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Encaixar à esquerda"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Encaixar à direita"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 75c445c6f35c..7d728a03b4ec 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problemas com a câmera?\nToque para ajustar o enquadramento"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"O problema não foi corrigido?\nToque para reverter"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Não tem problemas com a câmera? Toque para dispensar."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Toque para abrir o menu do app"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Toque para mostrar vários apps juntos"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Volte para a tela cheia no menu do app"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Veja e faça mais"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Arraste outro app para dividir a tela"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gerenciar janelas"</string> <string name="close_text" msgid="4986518933445178928">"Fechar"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ajustar tela"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Não é possível redimensionar o app"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Ajustar à esquerda"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Ajustar à direita"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 3c763ea8a6a4..3985d9bc792a 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ai probleme cu camera foto?\nAtinge pentru a reîncadra"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nu ai remediat problema?\nAtinge pentru a reveni"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nu ai probleme cu camera foto? Atinge pentru a închide."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Atinge pentru a deschide meniul aplicației"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Atinge pentru a afișa mai multe aplicații împreună"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Revino la ecranul complet din meniul aplicației"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Vezi și fă mai multe"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Trage în altă aplicație pentru a folosi ecranul împărțit"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Gestionează ferestrele"</string> <string name="close_text" msgid="4986518933445178928">"Închide"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Deschide meniul"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Micșorează fereastra și fixeaz-o"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Aplicația nu poate fi redimensionată"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplicația nu poate fi mutată aici"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizează"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Trage la stânga"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Trage la dreapta"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index a6c20adc4c2e..58a196b4eecb 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблемы с камерой?\nНажмите, чтобы исправить."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Не помогло?\nНажмите, чтобы отменить изменения."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Нет проблем с камерой? Нажмите, чтобы закрыть."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Нажмите, чтобы открыть меню приложения"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Нажмите, чтобы на экране размещались сразу несколько приложений"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Вернуться из меню приложения в режим полного экрана"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Выполняйте несколько задач одновременно"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Перетащите сюда другое приложение, чтобы использовать разделение экрана."</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Управление окнами"</string> <string name="close_text" msgid="4986518933445178928">"Закрыть"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Открыть меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Свернуть"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Изменить размер приложения нельзя."</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложение нельзя сюда переместить"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Развернуть"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Привязать слева"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Привязать справа"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index ed762699bc49..6682b017f2cc 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"කැමරා ගැටලුද?\nයළි සවි කිරීමට තට්ටු කරන්න"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"එය විසඳුවේ නැතිද?\nප්රතිවර්තනය කිරීමට තට්ටු කරන්න"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"කැමරා ගැටලු නොමැතිද? ඉවත දැමීමට තට්ටු කරන්න"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"යෙදුම් මෙනුව විවෘත කිරීමට තට්ටු කරන්න"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"යෙදුම් කිහිපයක් එකට පෙන්වීමට තට්ටු කරන්න"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"යෙදුම් මෙනුවෙන් පූර්ණ තිරය වෙත ආපසු යන්න"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"බලන්න සහ තවත් දේ කරන්න"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"බෙදුම් තිරය සඳහා වෙනත් යෙදුමකට අදින්න"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"කවුළු කළමනාකරණය කරන්න"</string> <string name="close_text" msgid="4986518933445178928">"වසන්න"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"මෙනුව විවෘත කරන්න"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ස්නැප් තිරය"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"මෙම යෙදුම ප්රතිප්රමාණ කළ නොහැක"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"යෙදුම මෙතැනට ගෙන යා නොහැක"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"විහිදන්න"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"වමට ස්නැප් කරන්න"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"දකුණට ස්නැප් කරන්න"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index 529a6932c9ce..96e54f1caa68 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problémy s kamerou?\nKlepnutím znova upravte."</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nevyriešilo sa to?\nKlepnutím sa vráťte."</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nemáte problémy s kamerou? Klepnutím zatvoríte."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Klepnúť a otvoriť tak ponuku aplikácií"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Klepnúť a zobraziť tak viacero aplikácií naraz"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Prejsť späť na celú obrazovku z ponuky aplikácií"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Zobrazte si a zvládnite toho viac"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Rozdelenú obrazovku môžete použiť presunutím do inej aplikácie"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Správa okien"</string> <string name="close_text" msgid="4986518933445178928">"Zavrieť"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvoriť ponuku"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Zobraziť polovicu obrazovky"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Veľkosť tejto aplikácie sa nedá zmeniť"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikácia sa sem nedá presunúť"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovať"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Prichytiť vľavo"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Prichytiť vpravo"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index fe08e04b21a3..66c9b26de54e 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Težave s fotoaparatom?\nDotaknite se za vnovično prilagoditev"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"To ni odpravilo težave?\nDotaknite se za povrnitev"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nimate težav s fotoaparatom? Dotaknite se za opustitev."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Dotaknite se, če želite odpreti meni aplikacije"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Dotaknite se, če želite prikazati več aplikacij hkrati"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Nazaj v celozaslonski način iz menija aplikacije"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Oglejte si in naredite več"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Za razdeljeni zaslon povlecite sem še eno aplikacijo."</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje oken"</string> <string name="close_text" msgid="4986518933445178928">"Zapri"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Odpri meni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Pripni zaslon"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Velikosti te aplikacije ni mogoče spremeniti"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiraj"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pripni levo"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Pripni desno"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index a4a7e744c426..6e49999545a8 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Ka probleme me kamerën?\nTrokit për ta ripërshtatur"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Nuk u rregullua?\nTrokit për ta rikthyer"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Nuk ka probleme me kamerën? Trokit për ta shpërfillur."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Trokit për të hapur menynë e aplikacionit"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Trokit për të shfaqur disa aplikacone bashkë"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Kthehu tek ekrani i plotë nga menyja e aplikacionit"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Shiko dhe bëj më shumë"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Zvarrite në një aplikacion tjetër për ekranin e ndarë"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Menaxho dritaret"</string> <string name="close_text" msgid="4986518933445178928">"Mbyll"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Hap menynë"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Regjistro ekranin"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Përmasat e këtij aplikacioni nuk mund të ndryshohen"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacioni nuk mund të zhvendoset këtu"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimizo"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Zhvendos majtas"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Zhvendos djathtas"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 9545ccf7a810..bd2fb8c442f3 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Имате проблема са камером?\nДодирните да бисте поново уклопили"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблем није решен?\nДодирните да бисте вратили"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немате проблема са камером? Додирните да бисте одбацили."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Додирните да бисте отворили мени апликације"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Додирните да бисте приказали више апликација заједно"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Вратите се из менија апликације на приказ преко целог екрана"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Видите и урадите више"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Превуците другу апликацију да бисте користили подељени екран"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Управљајте прозорима"</string> <string name="close_text" msgid="4986518933445178928">"Затворите"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отворите мени"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Уклопи екран"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Величина ове апликације не може да се промени"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликација не може да се премести овде"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Увећајте"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Прикачите лево"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Прикачите десно"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index c43f5dfa7b02..2184ac6bfca7 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problem med kameran?\nTryck för att anpassa på nytt"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Löstes inte problemet?\nTryck för att återställa"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Inga problem med kameran? Tryck för att ignorera."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Tryck för att öppna appmenyn"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Tryck för att visa flera appar tillsammans"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Återgå till helskärm från appmenyn"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Se och gör mer"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Dra till en annan app för att dela upp skärmen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Hantera fönster"</string> <string name="close_text" msgid="4986518933445178928">"Stäng"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Öppna menyn"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fäst skärmen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Det går inte att ändra storlek på appen"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Det går inte att flytta appen hit"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Utöka"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fäst till vänster"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Fäst till höger"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index c6ce023a5ca7..6068bf00a6df 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Je, kuna hitilafu za kamera?\nGusa ili urekebishe"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Umeshindwa kurekebisha?\nGusa ili urejeshe nakala ya awali"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Je, hakuna hitilafu za kamera? Gusa ili uondoe."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Gusa ili ufungue menyu ya programu"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Gusa ili uonyeshe programu nyingi kwa pamoja"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Rudi kwenye skrini nzima katika menyu ya programu"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Angalia na ufanye zaidi"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Buruta katika programu nyingine ili utumie skrini iliyogawanywa"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Dhibiti Windows"</string> <string name="close_text" msgid="4986518933445178928">"Funga"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Fungua Menyu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Panga Madirisha kwenye Skrini"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Huwezi kubadilisha ukubwa wa programu hii"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Imeshindwa kuhamishia programu hapa"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Panua"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Telezesha kushoto"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Telezesha kulia"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 2c2f31978b0f..a14abac75245 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"கேமரா தொடர்பான சிக்கல்களா?\nமீண்டும் பொருத்த தட்டவும்"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ஆப்ஸ் மெனுவைத் திறக்க தட்டவும்"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"பல ஆப்ஸை ஒன்றாகக் காட்ட தட்டவும்"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ஆப்ஸ் மெனுவில் இருந்து முழுத்திரைக்குச் செல்லும்"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"பலவற்றைப் பார்த்தல் மற்றும் செய்தல்"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"திரைப் பிரிப்புக்கு மற்றொரு ஆப்ஸை இழுக்கலாம்"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"சாளரங்களை நிர்வகிக்கலாம்"</string> <string name="close_text" msgid="4986518933445178928">"மூடும்"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"மெனுவைத் திறக்கும்"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"திரையை ஸ்னாப் செய்"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"இந்த ஆப்ஸின் அளவை மாற்ற முடியாது"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ஆப்ஸை இங்கே நகர்த்த முடியாது"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"பெரிதாக்கும்"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"இடதுபுறம் நகர்த்தும்"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"வலதுபுறம் நகர்த்தும்"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 8691c9d5aa26..84e76a8cc361 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"కెమెరా సమస్యలు ఉన్నాయా?\nరీఫిట్ చేయడానికి ట్యాప్ చేయండి"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"దాని సమస్యను పరిష్కరించలేదా?\nపూర్వస్థితికి మార్చడానికి ట్యాప్ చేయండి"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"కెమెరా సమస్యలు లేవా? తీసివేయడానికి ట్యాప్ చేయండి."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"యాప్ మెనూని తెరవడానికి ట్యాప్ చేయండి"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"పలు యాప్లను కలిపి చూడటానికి ట్యాప్ చేయండి"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"యాప్ మెనూ నుండి ఫుల్ స్క్రీన్కు తిరిగి రండి"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"చూసి, మరిన్ని చేయండి"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్లోకి లాగండి"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"విండోలను మేనేజ్ చేయండి"</string> <string name="close_text" msgid="4986518933445178928">"మూసివేయండి"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"మెనూను తెరవండి"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్ను పెంచండి"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"స్క్రీన్ను స్నాప్ చేయండి"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ఈ యాప్ సైజ్ను మార్చడం సాధ్యపడదు"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"యాప్ను ఇక్కడకి తరలించడం సాధ్యం కాదు"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"మ్యాగ్జిమైజ్ చేయండి"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ఎడమ వైపున స్నాప్ చేయండి"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"కుడి వైపున స్నాప్ చేయండి"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index 07e141601d08..856893f83955 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"หากพบปัญหากับกล้อง\nแตะเพื่อแก้ไข"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"หากไม่ได้แก้ไข\nแตะเพื่อเปลี่ยนกลับ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"หากไม่พบปัญหากับกล้อง แตะเพื่อปิด"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"แตะเพื่อเปิดเมนูแอป"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"แตะเพื่อแสดงแอปหลายรายการพร้อมกัน"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"กลับไปที่เต็มหน้าจอจากเมนูแอป"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"รับชมและทำสิ่งต่างๆ ได้มากขึ้น"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"ลากไปไว้ในแอปอื่นเพื่อแยกหน้าจอ"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"จัดการหน้าต่าง"</string> <string name="close_text" msgid="4986518933445178928">"ปิด"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"เปิดเมนู"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"สแนปหน้าจอ"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ปรับขนาดแอปนี้ไม่ได้"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ย้ายแอปมาที่นี่ไม่ได้"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ขยายใหญ่สุด"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"จัดพอดีกับทางซ้าย"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"จัดพอดีกับทางขวา"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 477700e7bbdf..dc92efd8e18f 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"May mga isyu sa camera?\nI-tap para i-refit"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Hindi ito naayos?\nI-tap para i-revert"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Walang isyu sa camera? I-tap para i-dismiss."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"I-tap para buksan ang menu ng app"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"I-tap para ipakita nang magkakasama ang maraming app"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Bumalik sa fullscreen mula sa menu ng app"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Tumingin at gumawa ng higit pa"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Mag-drag ng isa pang app para sa split screen"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Pamahalaan ang Mga Window"</string> <string name="close_text" msgid="4986518933445178928">"Isara"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buksan ang Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"I-snap ang Screen"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Hindi nare-resize ang app na ito"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"I-maximize"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"I-snap pakaliwa"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"I-snap pakanan"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index b0c253965970..e206cd6cd10c 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kameranızda sorun mu var?\nDüzeltmek için dokunun"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bu işlem sorunu düzeltmedi mi?\nİşlemi geri almak için dokunun"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kameranızda sorun yok mu? Kapatmak için dokunun."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Uygulama menüsünü açmak için dokunun"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Birden fazla uygulamayı birlikte göstermek için dokunun"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Uygulama menüsünden tam ekrana dönün"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Daha fazlasını görün ve yapın"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Bölünmüş ekran için başka bir uygulamayı sürükleyin"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Pencereleri yönet"</string> <string name="close_text" msgid="4986518933445178928">"Kapat"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menüyü aç"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranın Yarısına Tuttur"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Bu uygulama yeniden boyutlandırılamaz"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Uygulama buraya taşınamıyor"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ekranı kapla"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Sola tuttur"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Sağa tuttur"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index dd64c6642104..90a3bc33b3e2 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Проблеми з камерою?\nНатисніть, щоб пристосувати"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Проблему не вирішено?\nНатисніть, щоб скасувати зміни"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Немає проблем із камерою? Торкніться, щоб закрити."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Натисніть, щоб відкрити меню додатка"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Натисніть, щоб переглянути кілька додатків одночасно"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Повернутися з меню додатка в повноекранний режим"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Більше простору та можливостей"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Щоб перейти в режим розділення екрана, перетягніть сюди інший додаток"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Керувати вікнами"</string> <string name="close_text" msgid="4986518933445178928">"Закрити"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Відкрити меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Зафіксувати екран"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Розмір вікна цього додатка не можна змінити"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Сюди не можна перемістити додаток"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Розгорнути"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Закріпити ліворуч"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Закріпити праворуч"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index aa311c2978c8..2006b0ba6929 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"کیمرے کے مسائل؟\nدوبارہ فٹ کرنے کیلئے تھپتھپائیں"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"یہ حل نہیں ہوا؟\nلوٹانے کیلئے تھپتھپائیں"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"کوئی کیمرے کا مسئلہ نہیں ہے؟ برخاست کرنے کیلئے تھپتھپائیں۔"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"ایپ مینو کھولنے کیلئے تھپتھپائیں"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"متعدد ایپس ایک ساتھ دکھانے کیلئے تھپتھپائیں"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"ایپ مینو سے مکمل اسکرین پر واپس جائیں"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"دیکھیں اور بہت کچھ کریں"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"اسپلٹ اسکرین کے ليے دوسری ایپ میں گھسیٹیں"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Windows کا نظم کریں"</string> <string name="close_text" msgid="4986518933445178928">"بند کریں"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"مینو کھولیں"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"اسکرین کا اسناپ شاٹ لیں"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"اس ایپ کا سائز تبدیل نہیں کیا جا سکتا"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ایپ کو یہاں منتقل نہیں کیا جا سکتا"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"بڑا کریں"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"دائیں منتقل کریں"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"بائیں منتقل کریں"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 8754629df761..4b163f79f970 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Kamera nosozmi?\nQayta moslash uchun bosing"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Tuzatilmadimi?\nQaytarish uchun bosing"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Kamera muammosizmi? Yopish uchun bosing."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Ilova menyusini ochish uchun bosing"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Bir nechta ilovani birga chiqarish uchun bosing"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Ilova menyusidan butun ekranga qayting"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Yana boshqa amallar"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Ekranni ikkiga ajratish uchun boshqa ilovani bu yerga torting"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Oynalarni boshqarish"</string> <string name="close_text" msgid="4986518933445178928">"Yopish"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyuni ochish"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranni biriktirish"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Bu ilova hajmini oʻzgartirish imkonsiz"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ilova bu yerga surilmaydi"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Yoyish"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Chapga tortish"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Oʻngga tortish"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index abac3fe824bb..db86498130d8 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Có vấn đề với máy ảnh?\nHãy nhấn để sửa lỗi"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Bạn chưa khắc phục vấn đề?\nHãy nhấn để hủy bỏ"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Không có vấn đề với máy ảnh? Hãy nhấn để đóng."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Nhấn để mở trình đơn ứng dụng"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Nhấn để hiển thị nhiều ứng dụng cùng lúc"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Quay lại chế độ toàn màn hình từ trình đơn ứng dụng"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Xem và làm được nhiều việc hơn"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Kéo một ứng dụng khác vào để chia đôi màn hình"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Quản lý cửa sổ"</string> <string name="close_text" msgid="4986518933445178928">"Đóng"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Mở Trình đơn"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Điều chỉnh kích thước màn hình"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Không thể đổi kích thước của ứng dụng này"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Không di chuyển được ứng dụng đến đây"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Phóng to tối đa"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Di chuyển nhanh sang trái"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Di chuyển nhanh sang phải"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 5a61decdcfb9..ebf4b038b098 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相机有问题?\n点按即可整修"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"没有解决此问题?\n点按即可恢复"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相机没有问题?点按即可忽略。"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"点按可打开应用菜单"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"点按可同时显示多个应用"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"从应用菜单可返回到全屏"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"查看和处理更多任务"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一个应用,即可使用分屏模式"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"管理窗口"</string> <string name="close_text" msgid="4986518933445178928">"关闭"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打开菜单"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"屏幕快照"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"无法调整此应用的大小"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"无法将应用移至此处"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"贴靠左侧"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"贴靠右侧"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index e1c303e198bb..f1d12fce0103 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題?\n輕按即可修正"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未能修正問題?\n輕按即可還原"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機冇問題?㩒一下就可以即可閂咗佢。"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"輕按即可開啟應用程式選單"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"輕按即可同時顯示多個應用程式"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"從應用程式選單返回全螢幕"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖入另一個應用程式即可分割螢幕"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string> <string name="close_text" msgid="4986518933445178928">"關閉"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打開選單"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"此應用程式無法調整大小"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至這裡"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"貼齊左邊"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"貼齊右邊"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 97e36c300a4a..b5c28d347acb 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"相機有問題嗎?\n輕觸即可修正"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"未修正問題嗎?\n輕觸即可還原"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"相機沒問題嗎?輕觸即可關閉。"</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"輕觸即可開啟應用程式選單"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"輕觸即可一次顯示多個應用程式"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"從應用程式選單返回全螢幕"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"瀏覽更多內容及執行更多操作"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"拖進另一個應用程式即可使用分割畫面模式"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string> <string name="close_text" msgid="4986518933445178928">"關閉"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"開啟選單"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"貼齊畫面"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"這個應用程式無法調整大小"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至此處"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"靠左對齊"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"靠右對齊"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 74c7169fefa3..f54d0ed01ea8 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -97,6 +97,9 @@ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Izinkinga zekhamera?\nThepha ukuze uyilinganise kabusha"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Akuyilungisanga?\nThepha ukuze ubuyele"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Azikho izinkinga zekhamera? Thepha ukuze ucashise."</string> + <string name="windowing_app_handle_education_tooltip" msgid="6398482412956375783">"Thepha ukuze uvule imenyu ye-app"</string> + <string name="windowing_desktop_mode_image_button_education_tooltip" msgid="6285279585554484957">"Thepha ukuze ubonise ama-app amaningi ndawonye"</string> + <string name="windowing_desktop_mode_exit_education_tooltip" msgid="6685429075790085337">"Buyela esikrinini esigcwele ukusuka kumenyu ye-app"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Bona futhi wenze okuningi"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Hudula kwenye i-app mayelana nokuhlukanisa isikrini"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string> @@ -126,15 +129,21 @@ <string name="manage_windows_text" msgid="5567366688493093920">"Phatha Amawindi"</string> <string name="close_text" msgid="4986518933445178928">"Vala"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string> - <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) --> - <skip /> + <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Vula Imenyu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string> <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Thwebula Isikrini"</string> - <string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Le app ayikwazi ukushintshwa usayizi"</string> - <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) --> + <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"I-app ayikwazi ukuhanjiswa lapha"</string> + <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Khulisa"</string> + <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Chofoza kwesobunxele"</string> + <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Chofoza kwesokudla"</string> + <!-- no translation found for open_by_default_settings_text (2526548548598185500) --> + <skip /> + <!-- no translation found for open_by_default_dialog_subheader_text (1729599730664063881) --> + <skip /> + <!-- no translation found for open_by_default_dialog_in_app_text (6978022419634199806) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) --> + <!-- no translation found for open_by_default_dialog_in_browser_text (8042769465958497081) --> <skip /> - <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) --> + <!-- no translation found for open_by_default_dialog_dismiss_button_text (3487238795534582291) --> <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 3d8718332199..1f1565160965 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -608,6 +608,9 @@ <!-- The horizontal inset to apply to the close button's ripple drawable --> <dimen name="desktop_mode_header_close_ripple_inset_horizontal">6dp</dimen> + <!-- The padding added to all sides of windowing education tooltip --> + <dimen name="desktop_windowing_education_tooltip_padding">8dp</dimen> + <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) --> <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item> <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) --> @@ -621,4 +624,12 @@ <!-- The offset from the left edge of the entering page for the cross-activity animation --> <dimen name="cross_activity_back_entering_start_offset">96dp</dimen> + <!-- The open by default settings dialog menu width. --> + <dimen name="open_by_default_settings_dialog_width">348dp</dimen> + <!-- The open by default settings dialog menu height. --> + <dimen name="open_by_default_settings_dialog_height">380dp</dimen> + <!-- The height of radio buttons in the open by default settings dialog. --> + <dimen name="open_by_default_settings_dialog_radio_button_height">64dp</dimen> + <!-- The width of radio buttons in the open by default settings dialog. --> + <dimen name="open_by_default_settings_dialog_radio_button_width">316dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 266eca8cd2ca..5ef843210bbf 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -219,6 +219,15 @@ compatibility control. [CHAR LIMIT=NONE] --> <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string> + <!-- App handle education tooltip text for tooltip pointing to app handle --> + <string name="windowing_app_handle_education_tooltip">Tap to open the app menu</string> + + <!-- App handle education tooltip text for tooltip pointing to windowing image button --> + <string name="windowing_desktop_mode_image_button_education_tooltip">Tap to show multiple apps together</string> + + <!-- App handle education tooltip text for tooltip pointing to app chip --> + <string name="windowing_desktop_mode_exit_education_tooltip">Return to fullscreen from the app menu</string> + <!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] --> <string name="letterbox_education_dialog_title">See and do more</string> @@ -314,4 +323,15 @@ <string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string> <!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] --> <string name="desktop_mode_maximize_menu_snap_right_button_text">Snap right</string> + + <!-- Accessibility text for open by default settings button [CHAR LIMIT=NONE] --> + <string name="open_by_default_settings_text">Open by default settings</string> + <!-- Subheader for open by default menu string. --> + <string name="open_by_default_dialog_subheader_text">Choose how to open web links for this app</string> + <!-- Text for open by default settings dialog option. --> + <string name="open_by_default_dialog_in_app_text">In the app</string> + <!-- Text for open by default settings dialog option. --> + <string name="open_by_default_dialog_in_browser_text">In your browser</string> + <!-- Text for open by default settings dialog dismiss button. --> + <string name="open_by_default_dialog_dismiss_button_text">OK</string> </resources> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java index 26aae2d2aa78..02a799189fa1 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java @@ -26,5 +26,11 @@ public interface FocusTransitionListener { /** * Called when a transition changes the top, focused display. */ - void onFocusedDisplayChanged(int displayId); + default void onFocusedDisplayChanged(int displayId) {} + + /** + * Called when the per-app or system-wide focus state has changed for a task. + */ + default void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay, + boolean isFocusedGlobally) {} } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java index 58766826bd3b..85dabce8ac20 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java @@ -30,29 +30,32 @@ import java.util.Objects; */ public class BubbleInfo implements Parcelable { - private String mKey; // Same key as the Notification + private final String mKey; // Same key as the Notification private int mFlags; // Flags from BubbleMetadata @Nullable - private String mShortcutId; - private int mUserId; - private String mPackageName; + private final String mShortcutId; + private final int mUserId; + private final String mPackageName; /** * All notification bubbles require a shortcut to be set on the notification, however, the * app could still specify an Icon and PendingIntent to use for the bubble. In that case * this icon will be populated. If the bubble is entirely shortcut based, this will be null. */ @Nullable - private Icon mIcon; + private final Icon mIcon; @Nullable - private String mTitle; + private final String mTitle; @Nullable - private String mAppName; - private boolean mIsImportantConversation; - private boolean mShowAppBadge; + private final String mAppName; + private final boolean mIsImportantConversation; + private final boolean mShowAppBadge; + @Nullable + private final ParcelableFlyoutMessage mParcelableFlyoutMessage; public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon, int userId, String packageName, @Nullable String title, @Nullable String appName, - boolean isImportantConversation, boolean showAppBadge) { + boolean isImportantConversation, boolean showAppBadge, + @Nullable ParcelableFlyoutMessage flyoutMessage) { mKey = key; mFlags = flags; mShortcutId = shortcutId; @@ -63,6 +66,7 @@ public class BubbleInfo implements Parcelable { mAppName = appName; mIsImportantConversation = isImportantConversation; mShowAppBadge = showAppBadge; + mParcelableFlyoutMessage = flyoutMessage; } private BubbleInfo(Parcel source) { @@ -76,6 +80,8 @@ public class BubbleInfo implements Parcelable { mAppName = source.readString(); mIsImportantConversation = source.readBoolean(); mShowAppBadge = source.readBoolean(); + mParcelableFlyoutMessage = source.readParcelable( + ParcelableFlyoutMessage.class.getClassLoader(), ParcelableFlyoutMessage.class); } public String getKey() { @@ -122,6 +128,11 @@ public class BubbleInfo implements Parcelable { return mShowAppBadge; } + @Nullable + public ParcelableFlyoutMessage getParcelableFlyoutMessage() { + return mParcelableFlyoutMessage; + } + /** * Whether this bubble is currently being hidden from the stack. */ @@ -180,6 +191,7 @@ public class BubbleInfo implements Parcelable { parcel.writeString(mAppName); parcel.writeBoolean(mIsImportantConversation); parcel.writeBoolean(mShowAppBadge); + parcel.writeParcelable(mParcelableFlyoutMessage, flags); } @NonNull diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/FlyoutDrawableLoader.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/FlyoutDrawableLoader.kt new file mode 100644 index 000000000000..5a1733094019 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/FlyoutDrawableLoader.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 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.shared.bubbles + +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.util.Log + +object FlyoutDrawableLoader { + + private const val TAG = "FlyoutDrawableLoader" + + /** Loads the flyout icon as a [Drawable]. */ + @JvmStatic + fun Icon?.loadFlyoutDrawable(context: Context): Drawable? { + if (this == null) return null + try { + if (this.type == Icon.TYPE_URI || this.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission( + context.packageName, + this.uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } + return loadDrawable(context) + } catch (e: Exception) { + Log.w(TAG, "loadFlyoutDrawable failed: ${e.message}") + return null + } + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt new file mode 100644 index 000000000000..294d5e552684 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/ParcelableFlyoutMessage.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 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.shared.bubbles + +import android.graphics.drawable.Icon +import android.os.Parcel +import android.os.Parcelable + +/** The contents of the flyout message to be passed to launcher for rendering in the bubble bar. */ +class ParcelableFlyoutMessage( + val icon: Icon?, + val title: String?, + val message: String?, +) : Parcelable { + + constructor( + parcel: Parcel + ) : this( + icon = parcel.readParcelable(Icon::class.java.classLoader), + title = parcel.readString(), + message = parcel.readString(), + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(icon, flags) + parcel.writeString(title) + parcel.writeString(message) + } + + override fun describeContents() = 0 + + companion object { + @JvmField + val CREATOR = + object : Parcelable.Creator<ParcelableFlyoutMessage> { + override fun createFromParcel(parcel: Parcel) = ParcelableFlyoutMessage(parcel) + + override fun newArray(size: Int) = arrayOfNulls<ParcelableFlyoutMessage>(size) + } + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 647a555ad169..0150bcdbd412 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -19,7 +19,7 @@ package com.android.wm.shell.shared.desktopmode; import android.annotation.NonNull; import android.content.Context; import android.os.SystemProperties; -import android.window.flags.DesktopModeFlags; +import android.window.DesktopModeFlags; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS new file mode 100644 index 000000000000..bfb6d4ac5849 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS @@ -0,0 +1,4 @@ +jeremysim@google.com +winsonc@google.com +peanutbutter@google.com +shuminghao@google.com diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java index 498dc8bdd24d..7f1e4a873f64 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java @@ -66,14 +66,54 @@ public class SplitScreenConstants { public @interface SplitPosition { } - /** A snap target in the first half of the screen, where the split is roughly 30-70. */ - public static final int SNAP_TO_30_70 = 0; + /** + * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT, + * only used on tablets. + */ + public static final int SNAP_TO_2_33_66 = 0; + + /** A snap target for two apps, where the split is 50-50. */ + public static final int SNAP_TO_2_50_50 = 1; + + /** + * A snap target for two apps, where the split is 66-33. With FLAG_ENABLE_FLEXIBLE_SPLIT, + * only used on tablets. + */ + public static final int SNAP_TO_2_66_33 = 2; - /** The 50-50 snap target */ - public static final int SNAP_TO_50_50 = 1; + /** + * A snap target for two apps, where the split is 90-10. The "10" app extends off the screen, + * and is actually the same size as the onscreen app, but the visible portion takes up 10% of + * the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, used on phones and foldables. + */ + public static final int SNAP_TO_2_90_10 = 3; - /** A snap target in the latter half of the screen, where the split is roughly 70-30. */ - public static final int SNAP_TO_70_30 = 2; + /** + * A snap target for two apps, where the split is 10-90. The "10" app extends off the screen, + * and is actually the same size as the onscreen app, but the visible portion takes up 10% of + * the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, used on phones and foldables. + */ + public static final int SNAP_TO_2_10_90 = 4; + + /** + * A snap target for three apps, where the split is 33-33-33. With FLAG_ENABLE_FLEXIBLE_SPLIT, + * only used on tablets. + */ + public static final int SNAP_TO_3_33_33_33 = 5; + + /** + * A snap target for three apps, where the split is 45-45-10. The "10" app extends off the + * screen, and is actually the same size as the onscreen apps, but the visible portion takes + * up 10% of the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, only used on unfolded foldables. + */ + public static final int SNAP_TO_3_45_45_10 = 6; + + /** + * A snap target for three apps, where the split is 10-45-45. The "10" app extends off the + * screen, and is actually the same size as the onscreen apps, but the visible portion takes + * up 10% of the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, only used on unfolded foldables. + */ + public static final int SNAP_TO_3_10_45_45 = 7; /** * These snap targets are used for split pairs in a stable, non-transient state. They may be @@ -81,9 +121,14 @@ public class SplitScreenConstants { * {@link SnapPosition}. */ @IntDef(prefix = { "SNAP_TO_" }, value = { - SNAP_TO_30_70, - SNAP_TO_50_50, - SNAP_TO_70_30 + SNAP_TO_2_33_66, + SNAP_TO_2_50_50, + SNAP_TO_2_66_33, + SNAP_TO_2_90_10, + SNAP_TO_2_10_90, + SNAP_TO_3_33_33_33, + SNAP_TO_3_45_45_10, + SNAP_TO_3_10_45_45, }) public @interface PersistentSnapPosition {} @@ -91,9 +136,14 @@ public class SplitScreenConstants { * Checks if the snapPosition in question is a {@link PersistentSnapPosition}. */ public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) { - return snapPosition == SNAP_TO_30_70 - || snapPosition == SNAP_TO_50_50 - || snapPosition == SNAP_TO_70_30; + return snapPosition == SNAP_TO_2_33_66 + || snapPosition == SNAP_TO_2_50_50 + || snapPosition == SNAP_TO_2_66_33 + || snapPosition == SNAP_TO_2_90_10 + || snapPosition == SNAP_TO_2_10_90 + || snapPosition == SNAP_TO_3_33_33_33 + || snapPosition == SNAP_TO_3_45_45_10 + || snapPosition == SNAP_TO_3_10_45_45; } /** The divider doesn't snap to any target and is freely placeable. */ @@ -109,9 +159,14 @@ public class SplitScreenConstants { public static final int SNAP_TO_MINIMIZE = 13; @IntDef(prefix = { "SNAP_TO_" }, value = { - SNAP_TO_30_70, - SNAP_TO_50_50, - SNAP_TO_70_30, + SNAP_TO_2_33_66, + SNAP_TO_2_50_50, + SNAP_TO_2_66_33, + SNAP_TO_2_90_10, + SNAP_TO_2_10_90, + SNAP_TO_3_33_33_33, + SNAP_TO_3_45_45_10, + SNAP_TO_3_10_45_45, SNAP_TO_NONE, SNAP_TO_START_AND_DISMISS, SNAP_TO_END_AND_DISMISS, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags new file mode 100644 index 000000000000..db960d15c526 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags @@ -0,0 +1,11 @@ +# See system/logging/logcat/event.logtags for a description of the format of this file. + +option java_package com.android.wm.shell + +# Do not change these names without updating the checkin_events setting in +# google3/googledata/wireless/android/provisioning/gservices.config !! +# + +38500 wm_shell_enter_desktop_mode (EnterReason|1|5),(SessionId|1|5) +38501 wm_shell_exit_desktop_mode (ExitReason|1|5),(SessionId|1|5) +38502 wm_shell_desktop_mode_task_update (TaskEvent|1|5),(InstanceId|1|5),(uid|1|5),(TaskHeight|1),(TaskWidth|1),(TaskX|1),(TaskY|1),(SessionId|1|5),(MinimiseReason|1|5),(UnminimiseReason|1|5),(VisibleTaskCount|1) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt index 05ce36120c4f..65132fe89063 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt @@ -20,10 +20,17 @@ package com.android.wm.shell.apptoweb import android.content.Context import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.PackageManager +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationUserState import android.net.Uri +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.protolog.ShellProtoLogGroup -private val browserIntent = Intent() +private const val TAG = "AppToWebUtils" + +private val GenericBrowserIntent = Intent() .setAction(Intent.ACTION_VIEW) .addCategory(Intent.CATEGORY_BROWSABLE) .setData(Uri.parse("http:")) @@ -32,9 +39,9 @@ private val browserIntent = Intent() * Returns a boolean indicating whether a given package is a browser app. */ fun isBrowserApp(context: Context, packageName: String, userId: Int): Boolean { - browserIntent.setPackage(packageName) + GenericBrowserIntent.setPackage(packageName) val list = context.packageManager.queryIntentActivitiesAsUser( - browserIntent, PackageManager.MATCH_ALL, userId + GenericBrowserIntent, PackageManager.MATCH_ALL, userId ) list.forEach { @@ -44,3 +51,38 @@ fun isBrowserApp(context: Context, packageName: String, userId: Int): Boolean { } return false } + +/** + * Returns intent if there is a browser application available to handle the uri. Otherwise, returns + * null. + */ +fun getBrowserIntent(uri: Uri, packageManager: PackageManager): Intent? { + val intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER) + .setData(uri) + .addFlags(FLAG_ACTIVITY_NEW_TASK) + // If there is no browser application available to handle intent, return null + val component = intent.resolveActivity(packageManager) ?: return null + intent.setComponent(component) + return intent +} + +/** + * Returns the [DomainVerificationUserState] of the user associated with the given + * [DomainVerificationManager] and the given package. + */ +fun getDomainVerificationUserState( + manager: DomainVerificationManager, + packageName: String +): DomainVerificationUserState? { + try { + return manager.getDomainVerificationUserState(packageName) + } catch (e: PackageManager.NameNotFoundException) { + ProtoLog.w( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "%s: Failed to get domain verification user state: %s", + TAG, + e.message!! + ) + return null + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt new file mode 100644 index 000000000000..a727b54b3a3f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2024 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.apptoweb + +import android.app.ActivityManager.RunningTaskInfo +import android.app.TaskInfo +import android.content.Context +import android.content.pm.verify.domain.DomainVerificationManager +import android.graphics.Bitmap +import android.graphics.PixelFormat +import android.view.LayoutInflater +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE +import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL +import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL +import android.view.WindowlessWindowManager +import android.widget.ImageView +import android.widget.RadioButton +import android.widget.TextView +import android.window.TaskConstants +import com.android.wm.shell.R +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer +import java.util.function.Supplier + + +/** + * Window manager for the open by default settings dialog + */ +internal class OpenByDefaultDialog( + private val context: Context, + private val taskInfo: TaskInfo, + private val taskSurface: SurfaceControl, + private val displayController: DisplayController, + private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, + private val listener: DialogLifecycleListener, + appIconBitmap: Bitmap?, + appName: CharSequence? +) { + private lateinit var dialog: OpenByDefaultDialogView + private lateinit var viewHost: SurfaceControlViewHost + private lateinit var dialogSurfaceControl: SurfaceControl + private var dialogContainer: AdditionalViewHostViewContainer? = null + private lateinit var appIconView: ImageView + private lateinit var appNameView: TextView + + private lateinit var openInAppButton: RadioButton + private lateinit var openInBrowserButton: RadioButton + + private val domainVerificationManager = + context.getSystemService(DomainVerificationManager::class.java)!! + private val packageName = taskInfo.baseActivity?.packageName!! + + + init { + createDialog() + initializeRadioButtons() + bindAppInfo(appIconBitmap, appName) + } + + /** Creates an open by default settings dialog. */ + fun createDialog() { + val t = SurfaceControl.Transaction() + val taskBounds = taskInfo.configuration.windowConfiguration.bounds + + dialog = LayoutInflater.from(context) + .inflate( + R.layout.open_by_default_settings_dialog, + null /* root */ + ) as OpenByDefaultDialogView + appIconView = dialog.requireViewById(R.id.application_icon) + appNameView = dialog.requireViewById(R.id.application_name) + + val display = displayController.getDisplay(taskInfo.displayId) + val builder: SurfaceControl.Builder = SurfaceControl.Builder() + dialogSurfaceControl = builder + .setName("Open by Default Dialog of Task=" + taskInfo.taskId) + .setContainerLayer() + .setParent(taskSurface) + .setCallsite("OpenByDefaultDialog#createDialog") + .build() + t.setPosition(dialogSurfaceControl, 0f, 0f) + .setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height()) + .setLayer(dialogSurfaceControl, TaskConstants.TASK_CHILD_LAYER_SETTINGS_DIALOG) + .show(dialogSurfaceControl) + val lp = WindowManager.LayoutParams( + taskBounds.width(), + taskBounds.height(), + TYPE_APPLICATION_PANEL, + FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL, + PixelFormat.TRANSLUCENT) + lp.title = "Open by default settings dialog of task=" + taskInfo.taskId + lp.setTrustedOverlay() + val windowManager = WindowlessWindowManager( + taskInfo.configuration, + dialogSurfaceControl, null /* hostInputToken */ + ) + viewHost = SurfaceControlViewHost(context, display, windowManager, "Dialog").apply { + setView(dialog, lp) + rootSurfaceControl.applyTransactionOnDraw(t) + } + dialogContainer = AdditionalViewHostViewContainer( + dialogSurfaceControl, viewHost, surfaceControlTransactionSupplier) + + dialog.setDismissOnClickListener{ + closeMenu() + } + + dialog.setConfirmButtonClickListener { + setDefaultLinkHandlingSetting() + closeMenu() + } + + listener.onDialogCreated() + } + + private fun initializeRadioButtons() { + openInAppButton = dialog.requireViewById(R.id.open_in_app_button) + openInBrowserButton = dialog.requireViewById(R.id.open_in_browser_button) + + val userState = + getDomainVerificationUserState(domainVerificationManager, packageName) ?: return + val openInApp = userState.isLinkHandlingAllowed + openInAppButton.isChecked = openInApp + openInBrowserButton.isChecked = !openInApp + } + + private fun setDefaultLinkHandlingSetting() { + domainVerificationManager.setDomainVerificationLinkHandlingAllowed( + packageName, openInAppButton.isChecked) + } + + private fun closeMenu() { + dialogContainer?.releaseView() + dialogContainer = null + listener.onDialogDismissed() + } + + private fun bindAppInfo( + appIconBitmap: Bitmap?, + appName: CharSequence? + ) { + appIconView.setImageBitmap(appIconBitmap) + appNameView.text = appName + } + + /** + * Relayout the dialog to the new task bounds. + */ + fun relayout( + taskInfo: RunningTaskInfo, + ) { + val t = surfaceControlTransactionSupplier.get() + val taskBounds = taskInfo.configuration.windowConfiguration.bounds + t.setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height()) + viewHost.rootSurfaceControl.applyTransactionOnDraw(t) + viewHost.relayout(taskBounds.width(), taskBounds.height()) + } + + /** + * Defines interface for classes that can listen to lifecycle events of open by default settings + * dialog. + */ + interface DialogLifecycleListener { + /** Called when open by default dialog view has been created. */ + fun onDialogCreated() + + /** Called when open by default dialog view has been released. */ + fun onDialogDismissed() + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt new file mode 100644 index 000000000000..1b914f419d94 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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.apptoweb +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import android.widget.Button +import androidx.constraintlayout.widget.ConstraintLayout +import com.android.wm.shell.R + +/** View for open by default settings dialog for an application which allows the user to change + * where links will open by default, in the default browser or in the application. */ +class OpenByDefaultDialogView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { + + private lateinit var dialogContainer: View + private lateinit var backgroundDim: Drawable + + fun setDismissOnClickListener(callback: (View) -> Unit) { + // Clicks on the background dim should also dismiss the dialog. + setOnClickListener(callback) + // We add a no-op on-click listener to the dialog container so that clicks on it won't + // propagate to the listener of the layout (which represents the background dim). + dialogContainer.setOnClickListener { } + } + + fun setConfirmButtonClickListener(callback: (View) -> Unit) { + val dismissButton = dialogContainer.requireViewById<Button>( + R.id.open_by_default_settings_dialog_confirm_button + ) + dismissButton.setOnClickListener(callback) + } + + override fun onFinishInflate() { + super.onFinishInflate() + dialogContainer = requireViewById(R.id.open_by_default_dialog_container) + backgroundDim = background.mutate() + backgroundDim.alpha = 128 + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index df80946a99aa..7c5f71647843 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -33,8 +33,6 @@ public interface BackAnimation { * * @param touchX the X touch position of the {@link MotionEvent}. * @param touchY the Y touch position of the {@link MotionEvent}. - * @param velocityX the X velocity computed from the {@link MotionEvent}. - * @param velocityY the Y velocity computed from the {@link MotionEvent}. * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to * the process. This is forwarded separately because the input pipeline may mutate * the {#event} action state later. @@ -43,8 +41,6 @@ public interface BackAnimation { void onBackMotion( float touchX, float touchY, - float velocityX, - float velocityY, int keyAction, @BackEvent.SwipeEdge int swipeEdge); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 3e5adf395cdd..b90e6e2fab8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -352,16 +352,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont public void onBackMotion( float touchX, float touchY, - float velocityX, - float velocityY, int keyAction, @BackEvent.SwipeEdge int swipeEdge ) { mShellExecutor.execute(() -> onMotionEvent( /* touchX = */ touchX, /* touchY = */ touchY, - /* velocityX = */ velocityX, - /* velocityY = */ velocityY, /* keyAction = */ keyAction, /* swipeEdge = */ swipeEdge)); } @@ -500,14 +496,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont public void onMotionEvent( float touchX, float touchY, - float velocityX, - float velocityY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) { BackTouchTracker activeTouchTracker = getActiveTracker(); if (activeTouchTracker != null) { - activeTouchTracker.update(touchX, touchY, velocityX, velocityY); + activeTouchTracker.update(touchX, touchY); } // two gestures are waiting to be processed at the moment, skip any further user touches @@ -1501,10 +1495,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont int rootIdx = -1; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change c = info.getChanges().get(i); - if (c.hasFlags(FLAG_IS_WALLPAPER)) { - st.setAlpha(c.getLeash(), 1.0f); - continue; - } if (TransitionUtil.isOpeningMode(c.getMode())) { final Point offset = c.getEndRelOffset(); st.setPosition(c.getLeash(), offset.x, offset.y); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 169361ad5f6b..e3fc5c2273e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -56,6 +56,7 @@ import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.bubbles.BubbleInfo; +import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage; import java.io.PrintWriter; import java.util.List; @@ -350,7 +351,22 @@ public class Bubble implements BubbleViewProvider { getTitle(), getAppName(), isImportantConversation(), - !isAppLaunchIntent()); + !isAppLaunchIntent(), + getParcelableFlyoutMessage()); + } + + /** Creates a parcelable flyout message to send to launcher. */ + @Nullable + private ParcelableFlyoutMessage getParcelableFlyoutMessage() { + if (mFlyoutMessage == null) { + return null; + } + // the icon is only used in group chats + Icon icon = mFlyoutMessage.isGroupChat ? mFlyoutMessage.senderIcon : null; + String title = + mFlyoutMessage.senderName == null ? null : mFlyoutMessage.senderName.toString(); + String message = mFlyoutMessage.message == null ? null : mFlyoutMessage.message.toString(); + return new ParcelableFlyoutMessage(icon, title, message); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index af4a0c55f28d..a8a8c2a80974 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -274,7 +274,8 @@ public class BubbleController implements ConfigurationChangeListener, private final DragAndDropController mDragAndDropController; /** Used to send bubble events to launcher. */ private Bubbles.BubbleStateListener mBubbleStateListener; - + /** Used to track previous navigation mode to detect switch to buttons navigation. */ + private boolean mIsPrevNavModeGestures; /** Used to send updates to the views from {@link #mBubbleDataListener}. */ private BubbleViewCallback mBubbleViewCallback; @@ -356,6 +357,7 @@ public class BubbleController implements ConfigurationChangeListener, } }; mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this); + mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext); } private void registerOneHandedState(OneHandedController oneHanded) { @@ -589,6 +591,13 @@ public class BubbleController implements ConfigurationChangeListener, */ private void sendInitialListenerUpdate() { if (mBubbleStateListener != null) { + boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext); + if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) { + BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext) + ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT; + mBubblePositioner.setBubbleBarLocation(navButtonsLocation); + } + mIsPrevNavModeGestures = isCurrentNavModeGestures; BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar(); mBubbleStateListener.onBubbleStateChange(update); } @@ -2001,6 +2010,10 @@ public class BubbleController implements ConfigurationChangeListener, // in bubble bar mode, let the request to show the expanded view come from launcher. // only collapse here if we're collapsing. if (mLayerView != null && !isExpanded) { + if (mBubblePositioner.isImeVisible()) { + // If we're collapsing, hide the IME + hideCurrentInputMethod(); + } mLayerView.collapse(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java index c88a58be1461..1abe11998500 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java @@ -36,6 +36,8 @@ public class BubbleLogger { @VisibleForTesting public enum Event implements UiEventLogger.UiEventEnum { + // region bubble events + @UiEvent(doc = "User dismissed the bubble via gesture, add bubble to overflow.") BUBBLE_OVERFLOW_ADD_USER_GESTURE(483), @@ -64,7 +66,89 @@ public class BubbleLogger { BUBBLE_OVERFLOW_SELECTED(600), @UiEvent(doc = "Restore bubble to overflow after phone reboot.") - BUBBLE_OVERFLOW_RECOVER(691); + BUBBLE_OVERFLOW_RECOVER(691), + + // endregion + + // region bubble bar events + + @UiEvent(doc = "new bubble posted") + BUBBLE_BAR_BUBBLE_POSTED(1927), + + @UiEvent(doc = "existing bubble updated") + BUBBLE_BAR_BUBBLE_UPDATED(1928), + + @UiEvent(doc = "expanded a bubble from bubble bar") + BUBBLE_BAR_EXPANDED(1929), + + @UiEvent(doc = "bubble bar collapsed") + BUBBLE_BAR_COLLAPSED(1930), + + @UiEvent(doc = "dismissed single bubble from bubble bar by dragging it to dismiss target") + BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE(1931), + + @UiEvent(doc = "dismissed single bubble from bubble bar by dragging the expanded view to " + + "dismiss target") + BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW(1932), + + @UiEvent(doc = "dismiss bubble from app handle menu") + BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU(1933), + + @UiEvent(doc = "bubble is dismissed due to app finishing the bubble activity") + BUBBLE_BAR_BUBBLE_ACTIVITY_FINISH(1934), + + @UiEvent(doc = "dismissed the bubble bar by dragging it to dismiss target") + BUBBLE_BAR_DISMISSED_DRAG_BAR(1935), + + @UiEvent(doc = "bubble bar moved to the left edge of the screen by dragging from the " + + "expanded view") + BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW(1936), + + @UiEvent(doc = "bubble bar moved to the left edge of the screen by dragging from a single" + + " bubble") + BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE(1937), + + @UiEvent(doc = "bubble bar moved to the left edge of the screen by dragging the bubble bar") + BUBBLE_BAR_MOVED_LEFT_DRAG_BAR(1938), + + @UiEvent(doc = "bubble bar moved to the right edge of the screen by dragging from the " + + "expanded view") + BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW(1939), + + @UiEvent(doc = "bubble bar moved to the right edge of the screen by dragging from a " + + "single bubble") + BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE(1940), + + @UiEvent(doc = "bubble bar moved to the right edge of the screen by dragging the bubble " + + "bar") + BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR(1941), + + @UiEvent(doc = "stop bubbling conversation from app handle menu") + BUBBLE_BAR_APP_MENU_OPT_OUT(1942), + + @UiEvent(doc = "open app settings from app handle menu") + BUBBLE_BAR_APP_MENU_GO_TO_SETTINGS(1943), + + @UiEvent(doc = "flyout shown for a bubble") + BUBBLE_BAR_FLYOUT(1944), + + @UiEvent(doc = "notification for the bubble was canceled") + BUBBLE_BAR_BUBBLE_REMOVED_CANCELED(1945), + + @UiEvent(doc = "user turned off bubbles from settings") + BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED(1946), + + @UiEvent(doc = "bubble bar overflow opened") + BUBBLE_BAR_OVERFLOW_SELECTED(1947), + + @UiEvent(doc = "max number of bubbles was reached in bubble bar, move bubble to overflow") + BUBBLE_BAR_OVERFLOW_ADD_AGED(1948), + + @UiEvent(doc = "bubble promoted from overflow back to bubble bar") + BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR(1949), + + // endregion + ; private final int mId; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 2795881f0938..35a0d07a63b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -61,7 +61,6 @@ import android.view.ViewOutlineProvider; import android.view.ViewPropertyAnimator; import android.view.ViewTreeObserver; import android.view.WindowManager; -import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; @@ -91,10 +90,10 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.shared.bubbles.DismissView; -import com.android.wm.shell.shared.bubbles.RelativeTouchListener; import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.shared.animation.PhysicsAnimator; +import com.android.wm.shell.shared.bubbles.DismissView; +import com.android.wm.shell.shared.bubbles.RelativeTouchListener; import com.android.wm.shell.shared.magnetictarget.MagnetizedObject; import java.io.PrintWriter; @@ -2276,7 +2275,7 @@ public class BubbleStackView extends FrameLayout void startMonitoringSwipeUpGesture() { stopMonitoringSwipeUpGestureInternal(); - if (isGestureNavEnabled()) { + if (ContextUtils.isGestureNavigationMode(mContext)) { mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner); mBubblesNavBarGestureTracker.start(mSwipeUpListener); setOnTouchListener(mContainerSwipeListener); @@ -2311,12 +2310,6 @@ public class BubbleStackView extends FrameLayout } } - private boolean isGestureNavEnabled() { - return mContext.getResources().getInteger( - com.android.internal.R.integer.config_navBarInteractionMode) - == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; - } - /** * Stop monitoring for swipe up gesture */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 3982a237dd3b..39fb2f49c1ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -21,11 +21,10 @@ import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; +import static com.android.wm.shell.shared.bubbles.FlyoutDrawableLoader.loadFlyoutDrawable; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; @@ -34,7 +33,6 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; import android.util.Log; import android.util.PathParser; import android.view.LayoutInflater; @@ -51,7 +49,6 @@ import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.shared.handles.RegionSamplingHelper; import java.lang.ref.WeakReference; -import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; @@ -274,7 +271,7 @@ public class BubbleViewInfoTask { @Nullable BubbleExpandedView expandedView; int dotColor; Path dotPath; - @Nullable Bubble.FlyoutMessage flyoutMessage; + Bubble.FlyoutMessage flyoutMessage; Bitmap bubbleBitmap; Bitmap badgeBitmap; @@ -300,6 +297,10 @@ public class BubbleViewInfoTask { return null; } + // set the flyout message but don't load the avatar because we can't pass it on the + // binder to launcher + info.flyoutMessage = b.getFlyoutMessage(); + return info; } @@ -336,7 +337,7 @@ public class BubbleViewInfoTask { info.flyoutMessage = b.getFlyoutMessage(); if (info.flyoutMessage != null) { info.flyoutMessage.senderAvatar = - loadSenderAvatar(c, info.flyoutMessage.senderIcon); + loadFlyoutDrawable(info.flyoutMessage.senderIcon, c); } return info; } @@ -418,21 +419,4 @@ public class BubbleViewInfoTask { Color.WHITE, WHITE_SCRIM_ALPHA); return true; } - - @Nullable - static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { - Objects.requireNonNull(context); - if (icon == null) return null; - try { - if (icon.getType() == Icon.TYPE_URI - || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - return icon.loadDrawable(context); - } catch (Exception e) { - Log.w(TAG, "loadSenderAvatar failed: " + e.getMessage()); - return null; - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java index 1b7bb0db6516..e9a593392dc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java @@ -20,11 +20,10 @@ import static com.android.wm.shell.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.wm.shell.shared.bubbles.FlyoutDrawableLoader.loadFlyoutDrawable; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; @@ -33,7 +32,6 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.util.Log; import android.util.PathParser; @@ -50,7 +48,6 @@ import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.shared.handles.RegionSamplingHelper; import java.lang.ref.WeakReference; -import java.util.Objects; import java.util.concurrent.Executor; /** @@ -181,7 +178,7 @@ public class BubbleViewInfoTaskLegacy extends @Nullable BubbleExpandedView expandedView; int dotColor; Path dotPath; - @Nullable Bubble.FlyoutMessage flyoutMessage; + Bubble.FlyoutMessage flyoutMessage; Bitmap bubbleBitmap; Bitmap badgeBitmap; @@ -221,6 +218,10 @@ public class BubbleViewInfoTaskLegacy extends return null; } + // set the flyout message but don't load the avatar because we can't pass it on the + // binder to launcher + info.flyoutMessage = b.getFlyoutMessage(); + return info; } @@ -260,7 +261,7 @@ public class BubbleViewInfoTaskLegacy extends info.flyoutMessage = b.getFlyoutMessage(); if (info.flyoutMessage != null) { info.flyoutMessage.senderAvatar = - loadSenderAvatar(c, info.flyoutMessage.senderIcon); + loadFlyoutDrawable(info.flyoutMessage.senderIcon, c); } return info; } @@ -342,21 +343,4 @@ public class BubbleViewInfoTaskLegacy extends Color.WHITE, WHITE_SCRIM_ALPHA); return true; } - - @Nullable - static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { - Objects.requireNonNull(context); - if (icon == null) return null; - try { - if (icon.getType() == Icon.TYPE_URI - || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - return icon.loadDrawable(context); - } catch (Exception e) { - Log.w(TAG, "loadSenderAvatar failed: " + e.getMessage()); - return null; - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt new file mode 100644 index 000000000000..0b36f452348a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ContextUtils.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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.bubbles + +import android.content.Context +import android.view.View +import android.view.WindowManagerPolicyConstants +import com.android.internal.R + +/** Simplifies accessing context fields. */ +object ContextUtils { + + /** Gets navigation mode. */ + @JvmStatic + val Context.navigationMode: Int + get() = resources.getInteger(R.integer.config_navBarInteractionMode) + + /** Returns whether the navigation mode is gestures. */ + @JvmStatic + val Context.isGestureNavigationMode: Boolean + get() = navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL + + /** Returns whether layout direction is rtl. */ + @JvmStatic + val Context.isRtl: Boolean + get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index ec235a5d84ab..2a9001728cc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -353,6 +353,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView if (isDragging != mIsDragging) { mIsDragging = isDragging; updateSamplingState(); + + if (isDragging && mPositioner.isImeVisible()) { + // Hide the IME when dragging begins + mManager.hideCurrentInputMethod(); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index c4082d9f649c..38087c066918 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -157,6 +157,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } + private void dispatchImeRequested(int displayId, boolean isRequested) { + synchronized (mPositionProcessors) { + for (ImePositionProcessor pp : mPositionProcessors) { + pp.onImeRequested(displayId, isRequested); + } + } + } + @ImePositionProcessor.ImeAnimationFlags private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, boolean show, boolean isFloating, SurfaceControl.Transaction t) { @@ -398,11 +406,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public void setImeInputTargetRequestedVisibility(boolean visible) { if (android.view.inputmethod.Flags.refactorInsetsController()) { mImeRequestedVisible = visible; + dispatchImeRequested(mDisplayId, mImeRequestedVisible); + // In the case that the IME becomes visible, but we have the control with leash // already (e.g., when focussing an editText in activity B, while and editText in // activity A is focussed), we will not get a call of #insetsControlChanged, and // therefore have to start the show animation from here startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */); + + setVisibleDirectly(mImeRequestedVisible || mAnimation != null); } } @@ -444,6 +456,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (imeSource == null || mImeSourceControl == null) { return; } + // TODO(b/353463205): For hide: this still has the statsToken from the previous show + // request final var statsToken = mImeSourceControl.getImeStatsToken(); startAnimation(show, forceRestart, statsToken); @@ -540,6 +554,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); if (seek) { mAnimation.setCurrentFraction((seekValue - startY) / (endY - startY)); + } else { + // In some cases the value in onAnimationStart is zero, therefore setting it + // explicitly to startY + mAnimation.setCurrentFraction(0); } mAnimation.addUpdateListener(animation -> { @@ -621,6 +639,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged ImeTracker.PHASE_WM_ANIMATION_RUNNING); t.hide(animatingLeash); removeImeSurface(mDisplayId); + if (android.view.inputmethod.Flags.refactorInsetsController()) { + setVisibleDirectly(false /* visible */); + } ImeTracker.forLogging().onHidden(mStatsToken); } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) { ImeTracker.forLogging().onShown(mStatsToken); @@ -645,13 +666,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged animatingControl.release(SurfaceControl::release); } }); - if (!show) { + if (!android.view.inputmethod.Flags.refactorInsetsController() && !show) { // When going away, queue up insets change first, otherwise any bounds changes // can have a "flicker" of ime-provided insets. setVisibleDirectly(false /* visible */); } mAnimation.start(); - if (show) { + if (!android.view.inputmethod.Flags.refactorInsetsController() && show) { // When showing away, queue up insets change last, otherwise any bounds changes // can have a "flicker" of ime-provided insets. setVisibleDirectly(true /* visible */); @@ -697,6 +718,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } /** + * Called when the IME was requested by an app + * + * @param isRequested {@code true} if the IME was requested to be visible + */ + default void onImeRequested(int displayId, boolean isRequested) { + } + + /** * Called when the IME position is starting to animate. * * @param hiddenTop The y position of the top of the IME surface when it is hidden. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index 7070ce99b24c..b83b5f341dda 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -22,6 +22,7 @@ import android.app.WindowConfiguration import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager +import android.graphics.PointF import android.graphics.Rect import android.os.RemoteException import android.util.DisplayMetrics @@ -29,10 +30,13 @@ import android.util.Log import android.util.Pair import android.util.TypedValue import android.window.TaskSnapshot +import android.window.TransitionInfo import com.android.internal.protolog.ProtoLog import com.android.wm.shell.Flags import com.android.wm.shell.protolog.ShellProtoLogGroup import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.floor import kotlin.math.roundToInt /** A class that includes convenience methods. */ @@ -163,6 +167,142 @@ object PipUtils { return Rect(left, top, left + width, top + height) } + /** + * Temporary rounding "outward" (ie. -1.2 -> -2) used for crop since it is an int. We lean + * outward since, usually, child surfaces are, themselves, cropped, so we'd prefer to avoid + * inadvertently cutting out content that would otherwise be visible. + */ + private fun roundOut(`val`: Float): Int { + return (if (`val` >= 0f) ceil(`val`) else floor(`val`)).toInt() + } + + /** + * Calculates the transform to apply on a UNTRANSFORMED (config-at-end) Activity surface in + * order for it's hint-rect to occupy the same task-relative position/dimensions as it would + * have at the end of the transition (post-configuration). + * + * This is intended to be used in tandem with [calcStartTransform] below applied to the parent + * task. Applying both transforms simultaneously should result in the appearance of nothing + * having happened yet. + * + * Only the task should be animated (into it's identity state) and then WMCore will reset the + * activity transform in sync with its new configuration upon finish. + * + * Usage example: + * calcEndTransform(pipActivity, pipTask, scale, pos); + * t.setScale(pipActivity.getLeash(), scale.x, scale.y); + * t.setPosition(pipActivity.getLeash(), pos.x, pos.y); + * + * @see calcStartTransform + */ + @JvmStatic + fun calcEndTransform(pipActivity: TransitionInfo.Change, pipTask: TransitionInfo.Change, + outScale: PointF, outPos: PointF) { + val actStartBounds = pipActivity.startAbsBounds + val actEndBounds = pipActivity.endAbsBounds + val taskEndBounds = pipTask.endAbsBounds + + var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint + if (hintRect == null) { + hintRect = Rect(actStartBounds) + hintRect.offsetTo(0, 0) + } + + // FA = final activity bounds (absolute) + // FT = final task bounds (absolute) + // SA = start activity bounds (absolute) + // H = source hint (relative to start activity bounds) + // We want to transform the activity so that when the task is at FT, H overlaps with FA + + // This scales the activity such that the hint rect has the same dimensions + // as the final activity bounds. + val hintToEndScaleX = (actEndBounds.width().toFloat()) / (hintRect.width().toFloat()) + val hintToEndScaleY = (actEndBounds.height().toFloat()) / (hintRect.height().toFloat()) + // top-left needs to be (FA.tl - FT.tl) - H.tl * hintToEnd . H is relative to the + // activity; so, for example, if shrinking H to FA (hintToEnd < 1), then the tl of the + // shrunk SA is closer to H than expected, so we need to reduce how much we offset SA + // to get H.tl to match. + val startActPosInTaskEndX = + (actEndBounds.left - taskEndBounds.left) - hintRect.left * hintToEndScaleX + val startActPosInTaskEndY = + (actEndBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY + outScale.set(hintToEndScaleX, hintToEndScaleY) + outPos.set(startActPosInTaskEndX, startActPosInTaskEndY) + } + + /** + * Calculates the transform and crop to apply on a Task surface in order for the config-at-end + * activity inside it (original-size activity transformed to match it's hint rect to the final + * Task bounds) to occupy the same world-space position/dimensions as it had before the + * transition. + * + * Intended to be used in tandem with [calcEndTransform]. + * + * Usage example: + * calcStartTransform(pipTask, scale, pos, crop); + * t.setScale(pipTask.getLeash(), scale.x, scale.y); + * t.setPosition(pipTask.getLeash(), pos.x, pos.y); + * t.setCrop(pipTask.getLeash(), crop); + * + * @see calcEndTransform + */ + @JvmStatic + fun calcStartTransform(pipTask: TransitionInfo.Change, outScale: PointF, + outPos: PointF, outCrop: Rect) { + val startBounds = pipTask.startAbsBounds + val taskEndBounds = pipTask.endAbsBounds + // For now, pip activity bounds always matches task bounds. If this ever changes, we'll + // need to get the activity offset. + val endBounds = taskEndBounds + var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint + if (hintRect == null) { + hintRect = Rect(startBounds) + hintRect.offsetTo(0, 0) + } + + // FA = final activity bounds (absolute) + // FT = final task bounds (absolute) + // SA = start activity bounds (absolute) + // H = source hint (relative to start activity bounds) + // We want to transform the activity so that when the task is at FT, H overlaps with FA + + // The scaling which takes the hint rect (H) in SA and matches it to FA + val hintToEndScaleX = (endBounds.width().toFloat()) / (hintRect.width().toFloat()) + val hintToEndScaleY = (endBounds.height().toFloat()) / (hintRect.height().toFloat()) + + // We want to set the transform on the END TASK surface to put the start activity + // back to where it was. + // First do backwards scale (which takes FA back to H) + val endToHintScaleX = 1f / hintToEndScaleX + val endToHintScaleY = 1f / hintToEndScaleY + // Then top-left needs to place FA (relative to the FT) at H (relative to SA): + // so -(FA.tl - FT.tl) + SA.tl + H.tl + // but we have scaled up the task, so anything that was "within" the task needs to + // be scaled: + // so -(FA.tl - FT.tl)*endToHint + SA.tl + H.tl + val endTaskPosForStartX = (-(endBounds.left - taskEndBounds.left) * endToHintScaleX + + startBounds.left + hintRect.left) + val endTaskPosForStartY = (-(endBounds.top - taskEndBounds.top) * endToHintScaleY + + startBounds.top + hintRect.top) + outScale.set(endToHintScaleX, endToHintScaleY) + outPos.set(endTaskPosForStartX, endTaskPosForStartY) + + // now need to set crop to reveal the non-hint stuff. Again, hintrect is relative, so + // we must apply outsets to reveal the *activity* content which is *inside* the task + // and thus is scaled (ie. if activity is scaled down, each task-level pixel exposes + // >1 activity-level pixels) + // For example, the topleft crop would be: + // (FA.tl - FT.tl) - H.tl * hintToEnd + // ^ activity within task + // bottomright can just use scaled activity size + // tl + scale(SA.size, hintToEnd) + outCrop.left = roundOut((endBounds.left - taskEndBounds.left) + - hintRect.left * hintToEndScaleX) + outCrop.top = roundOut((endBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY) + outCrop.right = roundOut(outCrop.left + startBounds.width() * hintToEndScaleX) + outCrop.bottom = roundOut(outCrop.top + startBounds.height() * hintToEndScaleY) + } + private var isPip2ExperimentEnabled: Boolean? = null /** @@ -181,4 +321,4 @@ object PipUtils { } return isPip2ExperimentEnabled as Boolean } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java index f7f45ae36eda..9f100facc163 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -19,9 +19,9 @@ package com.android.wm.shell.common.split; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_30_70; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_70_30; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE; @@ -283,10 +283,10 @@ public class DividerSnapAlgorithm { private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition, int bottomPosition, int dividerMax) { - maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70); + maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_2_33_66); addMiddleTarget(isHorizontalDivision); maybeAddTarget(bottomPosition, - dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30); + dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_2_66_33); } private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) { @@ -332,7 +332,7 @@ public class DividerSnapAlgorithm { private void addMiddleTarget(boolean isHorizontalDivision) { int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, mInsets, mDisplayWidth, mDisplayHeight, mDividerSize); - mTargets.add(new SnapTarget(position, SNAP_TO_50_50)); + mTargets.add(new SnapTarget(position, SNAP_TO_2_50_50)); } private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 4b55fd0f5527..83ffaf473999 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -1106,6 +1106,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange default void onDoubleTappedDivider() { } + /** + * Sets the excludedInsetsTypes for the IME in the root WindowContainer. + */ + void setExcludeImeInsets(boolean exclude); + /** Returns split position of the token. */ @SplitPosition int getSplitItemPosition(WindowContainerToken token); @@ -1305,6 +1310,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } @Override + public void onImeRequested(int displayId, boolean isRequested) { + if (displayId != mDisplayId) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "IME was set to requested=%s", + isRequested); + mSplitLayoutHandler.setExcludeImeInsets(true); + } + + @Override public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t) { if (displayId != mDisplayId || !mInitialized) { @@ -1356,6 +1369,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange setDividerInteractive(!mImeShown || !mHasImeFocus || isFloating, true, "onImeStartPositioning"); + if (android.view.inputmethod.Flags.refactorInsetsController()) { + if (mImeShown) { + mSplitLayoutHandler.setExcludeImeInsets(false); + } + } + return mTargetYOffset != mLastYOffset ? IME_ANIMATION_NO_ALPHA : 0; } @@ -1374,6 +1393,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange "Split IME animation ending, canceled=%b", cancel); onProgress(1.0f); mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); + if (android.view.inputmethod.Flags.refactorInsetsController()) { + if (!mImeShown) { + // The IME hide animation is started immediately and at that point, the IME + // insets are not yet set to hidden. Therefore only resetting the + // excludedTypes at the end of the animation. Note: InsetsPolicy will only + // set the IME height to zero, when it is visible. When it becomes invisible, + // we dispatch the insets (the height there is zero as well) + mSplitLayoutHandler.setExcludeImeInsets(false); + } + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 4d15605c756a..2128cbc144b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -27,7 +27,7 @@ import android.graphics.Rect; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; -import android.window.flags.DesktopModeFlags; +import android.window.DesktopModeFlags; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 4227a6e2903f..2a5a519272c7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -87,7 +87,7 @@ import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler; import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository; import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator; import com.android.wm.shell.desktopmode.DesktopMode; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; @@ -267,7 +267,7 @@ public abstract class WMShellBaseModule { Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler, Lazy<AccessibilityManager> accessibilityManager, CompatUIRepository compatUIRepository, - Optional<DesktopModeTaskRepository> desktopModeTaskRepository, + Optional<DesktopRepository> desktopRepository, @NonNull CompatUIState compatUIState, @NonNull CompatUIComponentIdGenerator componentIdGenerator, @NonNull CompatUIComponentFactory compatUIComponentFactory, @@ -281,7 +281,7 @@ public abstract class WMShellBaseModule { componentIdGenerator, compatUIComponentFactory, mainExecutor)); } final IntPredicate inDesktopModePredicate = - desktopModeTaskRepository.<IntPredicate>map(modeTaskRepository -> displayId -> + desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId -> modeTaskRepository.getVisibleTaskCount(displayId) > 0) .orElseGet(() -> displayId -> false); return Optional.of( @@ -707,14 +707,14 @@ public abstract class WMShellBaseModule { ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopModeTaskRepository> desktopModeTaskRepository, + Optional<DesktopRepository> desktopRepository, TaskStackTransitionObserver taskStackTransitionObserver, @ShellMainThread ShellExecutor mainExecutor ) { return Optional.ofNullable( RecentTasksController.create(context, shellInit, shellController, shellCommandHandler, taskStackListener, activityTaskManager, - desktopModeTaskRepository, taskStackTransitionObserver, mainExecutor)); + desktopRepository, taskStackTransitionObserver, mainExecutor)); } @BindsOptionalOf @@ -1003,16 +1003,16 @@ public abstract class WMShellBaseModule { @BindsOptionalOf @DynamicOverride - abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository(); + abstract DesktopRepository optionalDesktopRepository(); @WMSingleton @Provides - static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(Context context, - @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) { + static Optional<DesktopRepository> provideDesktopRepository(Context context, + @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. - return desktopModeTaskRepository.flatMap((lazy) -> { + return desktopRepository.flatMap((lazy) -> { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(lazy.get()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 96a07755fea9..52262e68c401 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -16,7 +16,8 @@ package com.android.wm.shell.dagger; -import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; +import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS; +import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; import android.annotation.Nullable; import android.app.KeyguardManager; @@ -28,6 +29,8 @@ import android.view.Choreographer; import android.view.IWindowManager; import android.view.WindowManager; +import androidx.annotation.OptIn; + import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; @@ -59,12 +62,16 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; +import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler; +import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler; +import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler; import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopTaskChangeListener; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver; @@ -85,6 +92,9 @@ import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskTransitionHandler; import com.android.wm.shell.freeform.FreeformTaskTransitionObserver; +import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.freeform.FreeformTaskTransitionStarterInitializer; +import com.android.wm.shell.freeform.TaskChangeListener; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipTransitionController; @@ -101,6 +111,7 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.DefaultMixedHandler; +import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.HomeTransitionObserver; import com.android.wm.shell.transition.MixedTransitionHandler; import com.android.wm.shell.transition.Transitions; @@ -116,6 +127,8 @@ import com.android.wm.shell.unfold.qualifier.UnfoldTransition; import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer; +import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController; import dagger.Binds; import dagger.Lazy; @@ -123,6 +136,8 @@ import dagger.Module; import dagger.Provides; import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.ExperimentalCoroutinesApi; +import kotlinx.coroutines.MainCoroutineDispatcher; import java.util.ArrayList; import java.util.List; @@ -234,6 +249,7 @@ public abstract class WMShellModule { IWindowManager windowManager, ShellCommandHandler shellCommandHandler, ShellTaskOrganizer taskOrganizer, + @DynamicOverride DesktopRepository desktopRepository, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -246,8 +262,10 @@ public abstract class WMShellModule { AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, + AppHandleEducationController appHandleEducationController, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, - Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) { + Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler, + FocusTransitionObserver focusTransitionObserver) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return new DesktopModeWindowDecorViewModel( context, @@ -259,6 +277,7 @@ public abstract class WMShellModule { shellCommandHandler, windowManager, taskOrganizer, + desktopRepository, displayController, shellController, displayInsetsController, @@ -271,8 +290,10 @@ public abstract class WMShellModule { assistContentRequester, multiInstanceHelper, desktopTasksLimiter, + appHandleEducationController, windowDecorCaptionHandleRepository, - desktopActivityOrientationHandler); + desktopActivityOrientationHandler, + focusTransitionObserver); } return new CaptionWindowDecorViewModel( context, @@ -286,7 +307,8 @@ public abstract class WMShellModule { displayController, rootTaskDisplayAreaOrganizer, syncQueue, - transitions); + transitions, + focusTransitionObserver); } @WMSingleton @@ -307,6 +329,11 @@ public abstract class WMShellModule { return new AssistContentRequester(context, shellExecutor, bgExecutor); } + @Provides + static AdditionalSystemViewContainer.Factory provideAdditionalSystemViewContainerFactory() { + return new AdditionalSystemViewContainer.Factory(); + } + // // Freeform // @@ -317,9 +344,13 @@ public abstract class WMShellModule { static FreeformComponents provideFreeformComponents( FreeformTaskListener taskListener, FreeformTaskTransitionHandler transitionHandler, - FreeformTaskTransitionObserver transitionObserver) { + FreeformTaskTransitionObserver transitionObserver, + FreeformTaskTransitionStarterInitializer transitionStarterInitializer) { return new FreeformComponents( - taskListener, Optional.of(transitionHandler), Optional.of(transitionObserver)); + taskListener, + Optional.of(transitionHandler), + Optional.of(transitionObserver), + Optional.of(transitionStarterInitializer)); } @WMSingleton @@ -328,7 +359,8 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<DesktopModeTaskRepository> desktopModeTaskRepository, + Optional<DesktopRepository> desktopRepository, + Optional<DesktopTasksController> desktopTasksController, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorViewModel) { // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic @@ -337,33 +369,22 @@ public abstract class WMShellModule { ? shellInit : null; return new FreeformTaskListener(context, init, shellTaskOrganizer, - desktopModeTaskRepository, launchAdjacentController, windowDecorViewModel); + desktopRepository, desktopTasksController, launchAdjacentController, + windowDecorViewModel); } @WMSingleton @Provides static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler( - ShellInit shellInit, Transitions transitions, - Context context, - WindowDecorViewModel windowDecorViewModel, DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor, - @ShellAnimationThread ShellExecutor animExecutor, - @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, - InteractionJankMonitor interactionJankMonitor, - @ShellMainThread Handler handler) { + @ShellAnimationThread ShellExecutor animExecutor) { return new FreeformTaskTransitionHandler( - shellInit, transitions, - context, - windowDecorViewModel, displayController, mainExecutor, - animExecutor, - desktopModeTaskRepository, - interactionJankMonitor, - handler); + animExecutor); } @WMSingleton @@ -372,16 +393,36 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { + Optional<DesktopFullImmersiveTransitionHandler> desktopImmersiveTransitionHandler, + WindowDecorViewModel windowDecorViewModel, + Optional<TaskChangeListener> taskChangeListener, + FocusTransitionObserver focusTransitionObserver) { return new FreeformTaskTransitionObserver( - context, shellInit, transitions, windowDecorViewModel); + context, shellInit, transitions, desktopImmersiveTransitionHandler, + windowDecorViewModel, taskChangeListener, focusTransitionObserver); + } + + @WMSingleton + @Provides + static FreeformTaskTransitionStarterInitializer provideFreeformTaskTransitionStarterInitializer( + ShellInit shellInit, + WindowDecorViewModel windowDecorViewModel, + FreeformTaskTransitionHandler freeformTaskTransitionHandler, + Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler) { + FreeformTaskTransitionStarter transitionStarter; + if (desktopMixedTransitionHandler.isPresent()) { + transitionStarter = desktopMixedTransitionHandler.get(); + } else { + transitionStarter = freeformTaskTransitionHandler; + } + return new FreeformTaskTransitionStarterInitializer(shellInit, windowDecorViewModel, + transitionStarter); } // // One handed mode // - // Needs the shell main handler for ContentObserver callbacks @WMSingleton @Provides @@ -591,7 +632,8 @@ public abstract class WMShellModule { DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, DragToDesktopTransitionHandler dragToDesktopTransitionHandler, - @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, + @DynamicOverride DesktopRepository desktopRepository, + Optional<DesktopFullImmersiveTransitionHandler> desktopFullImmersiveTransitionHandler, DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, RecentsTransitionHandler recentsTransitionHandler, @@ -607,7 +649,8 @@ public abstract class WMShellModule { returnToDragStartAnimator, enterDesktopTransitionHandler, exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler, toggleResizeDesktopTaskTransitionHandler, - dragToDesktopTransitionHandler, desktopModeTaskRepository, + dragToDesktopTransitionHandler, desktopFullImmersiveTransitionHandler.get(), + desktopRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null), interactionJankMonitor, mainHandler); @@ -615,10 +658,20 @@ public abstract class WMShellModule { @WMSingleton @Provides + static Optional<TaskChangeListener> provideDesktopTaskChangeListener(Context context) { + if (Flags.enableWindowingTransitionHandlersObservers() && + DesktopModeStatus.canEnterDesktopMode(context)) { + return Optional.of(new DesktopTaskChangeListener()); + } + return Optional.empty(); + } + + @WMSingleton + @Provides static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter( Context context, Transitions transitions, - @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, + @DynamicOverride DesktopRepository desktopRepository, ShellTaskOrganizer shellTaskOrganizer, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { @@ -631,7 +684,7 @@ public abstract class WMShellModule { return Optional.of( new DesktopTasksLimiter( transitions, - desktopModeTaskRepository, + desktopRepository, shellTaskOrganizer, maxTaskLimit, interactionJankMonitor, @@ -642,6 +695,25 @@ public abstract class WMShellModule { @WMSingleton @Provides + static Optional<DesktopFullImmersiveTransitionHandler> provideDesktopImmersiveHandler( + Context context, + Transitions transitions, + @DynamicOverride DesktopRepository desktopRepository, + DisplayController displayController, + ShellTaskOrganizer shellTaskOrganizer) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { + return Optional.of( + new DesktopFullImmersiveTransitionHandler( + transitions, + desktopRepository, + displayController, + shellTaskOrganizer)); + } + return Optional.empty(); + } + + @WMSingleton + @Provides static ReturnToDragStartAnimator provideReturnToDragStartAnimator( Context context, InteractionJankMonitor interactionJankMonitor) { return new ReturnToDragStartAnimator(context, interactionJankMonitor); @@ -686,7 +758,17 @@ public abstract class WMShellModule { InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { return new ExitDesktopTaskTransitionHandler( - transitions, context, interactionJankMonitor, handler); + transitions, context, interactionJankMonitor, handler); + } + + @WMSingleton + @Provides + static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler( + Context context, + @ShellMainThread ShellExecutor mainExecutor, + @ShellAnimationThread ShellExecutor animExecutor + ) { + return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor); } @WMSingleton @@ -700,13 +782,14 @@ public abstract class WMShellModule { @WMSingleton @Provides @DynamicOverride - static DesktopModeTaskRepository provideDesktopModeTaskRepository( + + static DesktopRepository provideDesktopRepository( Context context, ShellInit shellInit, DesktopPersistentRepository desktopPersistentRepository, @ShellMainThread CoroutineScope mainScope ) { - return new DesktopModeTaskRepository(context, shellInit, desktopPersistentRepository, + return new DesktopRepository(context, shellInit, desktopPersistentRepository, mainScope); } @@ -718,12 +801,12 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, TaskStackListenerImpl taskStackListener, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, - @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository + @DynamicOverride DesktopRepository desktopRepository ) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(new DesktopActivityOrientationChangeHandler( context, shellInit, shellTaskOrganizer, taskStackListener, - toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository)); + toggleResizeDesktopTaskTransitionHandler, desktopRepository)); } return Optional.empty(); } @@ -732,12 +815,12 @@ public abstract class WMShellModule { @Provides static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver( Context context, - Optional<DesktopModeTaskRepository> desktopModeTaskRepository, + Optional<DesktopRepository> desktopRepository, Transitions transitions, ShellTaskOrganizer shellTaskOrganizer, ShellInit shellInit ) { - return desktopModeTaskRepository.flatMap(repository -> + return desktopRepository.flatMap(repository -> Optional.of(new DesktopTasksTransitionObserver( context, repository, transitions, shellTaskOrganizer, shellInit)) ); @@ -745,6 +828,32 @@ public abstract class WMShellModule { @WMSingleton @Provides + static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler( + Context context, + Transitions transitions, + @DynamicOverride DesktopRepository desktopRepository, + FreeformTaskTransitionHandler freeformTaskTransitionHandler, + CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler, + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler + ) { + if (!DesktopModeStatus.canEnterDesktopMode(context) + || !ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()) { + return Optional.empty(); + } + return Optional.of( + new DesktopMixedTransitionHandler( + context, + transitions, + desktopRepository, + freeformTaskTransitionHandler, + closeDesktopTaskTransitionHandler, + interactionJankMonitor, + handler)); + } + + @WMSingleton + @Provides static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver( Context context, ShellInit shellInit, @@ -783,13 +892,31 @@ public abstract class WMShellModule { @WMSingleton @Provides + static DesktopWindowingEducationTooltipController + provideDesktopWindowingEducationTooltipController( + Context context, + AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory, + DisplayController displayController + ) { + return new DesktopWindowingEducationTooltipController(context, + additionalSystemViewContainerFactory, displayController); + } + + @OptIn(markerClass = ExperimentalCoroutinesApi.class) + @WMSingleton + @Provides static AppHandleEducationController provideAppHandleEducationController( + Context context, AppHandleEducationFilter appHandleEducationFilter, - ShellTaskOrganizer shellTaskOrganizer, AppHandleEducationDatastoreRepository appHandleEducationDatastoreRepository, - @ShellMainThread CoroutineScope applicationScope) { - return new AppHandleEducationController(appHandleEducationFilter, - shellTaskOrganizer, appHandleEducationDatastoreRepository, applicationScope); + WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, + DesktopWindowingEducationTooltipController desktopWindowingEducationTooltipController, + @ShellMainThread CoroutineScope applicationScope, @ShellBackgroundThread + MainCoroutineDispatcher backgroundDispatcher) { + return new AppHandleEducationController(context, appHandleEducationFilter, + appHandleEducationDatastoreRepository, windowDecorCaptionHandleRepository, + desktopWindowingEducationTooltipController, applicationScope, + backgroundDispatcher); } @WMSingleton @@ -841,8 +968,7 @@ public abstract class WMShellModule { @Provides static Object provideIndependentShellComponentsToCreate( DragAndDropController dragAndDropController, - Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, - AppHandleEducationController appHandleEducationController + Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional ) { return new Object(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 3464fef07f33..3508ecee6d51 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -77,10 +77,12 @@ public abstract class Pip2Module { PipTaskListener pipTaskListener, @NonNull PipScheduler pipScheduler, @NonNull PipTransitionState pipStackListenerController, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, @NonNull PipUiStateChangeController pipUiStateChangeController) { return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener, - pipScheduler, pipStackListenerController, pipUiStateChangeController); + pipScheduler, pipStackListenerController, pipDisplayLayoutState, + pipUiStateChangeController); } @WMSingleton @@ -125,11 +127,9 @@ public abstract class Pip2Module { @Provides static PipScheduler providePipScheduler(Context context, PipBoundsState pipBoundsState, - PhonePipMenuController pipMenuController, @ShellMainThread ShellExecutor mainExecutor, PipTransitionState pipTransitionState) { - return new PipScheduler(context, pipBoundsState, pipMenuController, - mainExecutor, pipTransitionState); + return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState); } @WMSingleton @@ -138,10 +138,13 @@ public abstract class Pip2Module { PipBoundsState pipBoundsState, PipMediaController pipMediaController, SystemWindows systemWindows, PipUiEventLogger pipUiEventLogger, + PipTaskListener pipTaskListener, + @NonNull PipTransitionState pipTransitionState, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, pipUiEventLogger, mainExecutor, mainHandler); + systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState, mainExecutor, + mainHandler); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt new file mode 100644 index 000000000000..a16c15dfdf1a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.Context +import android.graphics.Rect +import android.os.IBinder +import android.util.TypedValue +import android.view.SurfaceControl.Transaction +import android.view.WindowManager +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import androidx.core.animation.addListener +import com.android.app.animation.Interpolators +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.transition.Transitions +import java.util.function.Supplier + +/** The [Transitions.TransitionHandler] that handles transitions for closing desktop mode tasks. */ +class CloseDesktopTaskTransitionHandler +@JvmOverloads +constructor( + private val context: Context, + private val mainExecutor: ShellExecutor, + private val animExecutor: ShellExecutor, + private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, +) : Transitions.TransitionHandler { + + private val runningAnimations = mutableMapOf<IBinder, List<Animator>>() + + /** Returns null, as it only handles transitions started from Shell. */ + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo, + ): WindowContainerTransaction? = null + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: Transaction, + finishTransaction: Transaction, + finishCallback: Transitions.TransitionFinishCallback, + ): Boolean { + if (info.type != WindowManager.TRANSIT_CLOSE) return false + val animations = mutableListOf<Animator>() + val onAnimFinish: (Animator) -> Unit = { animator -> + mainExecutor.execute { + // Animation completed + animations.remove(animator) + if (animations.isEmpty()) { + // All animations completed, finish the transition + runningAnimations.remove(transition) + finishCallback.onTransitionFinished(/* wct= */ null) + } + } + } + animations += + info.changes + .filter { + it.mode == WindowManager.TRANSIT_CLOSE && + it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM + } + .map { createCloseAnimation(it, finishTransaction, onAnimFinish) } + if (animations.isEmpty()) return false + runningAnimations[transition] = animations + animExecutor.execute { animations.forEach(Animator::start) } + return true + } + + private fun createCloseAnimation( + change: TransitionInfo.Change, + finishTransaction: Transaction, + onAnimFinish: (Animator) -> Unit, + ): Animator { + finishTransaction.hide(change.leash) + return AnimatorSet().apply { + playTogether(createBoundsCloseAnimation(change), createAlphaCloseAnimation(change)) + addListener(onEnd = onAnimFinish) + } + } + + private fun createBoundsCloseAnimation(change: TransitionInfo.Change): Animator { + val startBounds = change.startAbsBounds + val endBounds = + Rect(startBounds).apply { + // Scale the end bounds of the window down with an anchor in the center + inset( + (startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(), + (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt() + ) + val offsetY = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + CLOSE_ANIM_OFFSET_Y, + context.resources.displayMetrics + ) + .toInt() + offset(/* dx= */ 0, offsetY) + } + return ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds).apply { + duration = CLOSE_ANIM_DURATION_BOUNDS + interpolator = Interpolators.STANDARD_ACCELERATE + addUpdateListener { animation -> + val animBounds = animation.animatedValue as Rect + val animScale = 1 - (1 - CLOSE_ANIM_SCALE) * animation.animatedFraction + transactionSupplier + .get() + .setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat()) + .setScale(change.leash, animScale, animScale) + .apply() + } + } + } + + private fun createAlphaCloseAnimation(change: TransitionInfo.Change): Animator = + ValueAnimator.ofFloat(1f, 0f).apply { + duration = CLOSE_ANIM_DURATION_ALPHA + interpolator = Interpolators.LINEAR + addUpdateListener { animation -> + transactionSupplier + .get() + .setAlpha(change.leash, animation.animatedValue as Float) + .apply() + } + } + + private companion object { + const val CLOSE_ANIM_DURATION_BOUNDS = 200L + const val CLOSE_ANIM_DURATION_ALPHA = 100L + const val CLOSE_ANIM_SCALE = 0.95f + const val CLOSE_ANIM_OFFSET_Y = 36.0f + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt index 59e006879da8..606aa6cd3353 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt @@ -39,7 +39,7 @@ class DesktopActivityOrientationChangeHandler( private val shellTaskOrganizer: ShellTaskOrganizer, private val taskStackListener: TaskStackListenerImpl, private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler, - private val taskRepository: DesktopModeTaskRepository, + private val taskRepository: DesktopRepository, ) { init { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt new file mode 100644 index 000000000000..679179a7ff68 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo +import android.graphics.Rect +import android.os.IBinder +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE +import android.view.animation.DecelerateInterpolator +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import androidx.core.animation.addListener +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.protolog.ProtoLog +import com.android.window.flags.Flags +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TransitionHandler +import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener + +/** + * A [TransitionHandler] to move a task in/out of desktop's full immersive state where the task + * remains freeform while being able to take fullscreen bounds and have its App Header visibility + * be transient below the status bar like in fullscreen immersive mode. + */ +class DesktopFullImmersiveTransitionHandler( + private val transitions: Transitions, + private val desktopRepository: DesktopRepository, + private val displayController: DisplayController, + private val shellTaskOrganizer: ShellTaskOrganizer, + private val transactionSupplier: () -> SurfaceControl.Transaction, +) : TransitionHandler { + + constructor( + transitions: Transitions, + desktopRepository: DesktopRepository, + displayController: DisplayController, + shellTaskOrganizer: ShellTaskOrganizer, + ) : this( + transitions, + desktopRepository, + displayController, + shellTaskOrganizer, + { SurfaceControl.Transaction() } + ) + + private var state: TransitionState? = null + + @VisibleForTesting + val pendingExternalExitTransitions = mutableSetOf<ExternalPendingExit>() + + /** Whether there is an immersive transition that hasn't completed yet. */ + private val inProgress: Boolean + get() = state != null + + private val rectEvaluator = RectEvaluator() + + /** A listener to invoke on animation changes during entry/exit. */ + var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null + + /** Starts a transition to enter full immersive state inside the desktop. */ + fun moveTaskToImmersive(taskInfo: RunningTaskInfo) { + if (inProgress) { + logV("Cannot start entry because transition already in progress.") + return + } + val wct = WindowContainerTransaction().apply { + setBounds(taskInfo.token, Rect()) + } + logV("Moving task ${taskInfo.taskId} into immersive mode") + val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) + state = TransitionState( + transition = transition, + displayId = taskInfo.displayId, + taskId = taskInfo.taskId, + direction = Direction.ENTER + ) + } + + fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) { + if (inProgress) { + logV("Cannot start exit because transition already in progress.") + return + } + + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + val destinationBounds = calculateMaximizeBounds(displayLayout, taskInfo) + val wct = WindowContainerTransaction().apply { + setBounds(taskInfo.token, destinationBounds) + } + logV("Moving task ${taskInfo.taskId} out of immersive mode") + val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) + state = TransitionState( + transition = transition, + displayId = taskInfo.displayId, + taskId = taskInfo.taskId, + direction = Direction.EXIT + ) + } + + /** + * Bring the immersive app of the given [displayId] out of immersive mode, if applicable. + * + * @param transition that will apply this transaction + * @param wct that will apply these changes + * @param displayId of the display that should exit immersive mode + */ + fun exitImmersiveIfApplicable( + transition: IBinder, + wct: WindowContainerTransaction, + displayId: Int + ) { + if (!Flags.enableFullyImmersiveInDesktop()) return + exitImmersiveIfApplicable(wct, displayId)?.invoke(transition) + } + + /** + * Bring the immersive app of the given [displayId] out of immersive mode, if applicable. + * + * @param wct that will apply these changes + * @param displayId of the display that should exit immersive mode + * @return a function to apply once the transition that will apply these changes is started + */ + fun exitImmersiveIfApplicable( + wct: WindowContainerTransaction, + displayId: Int + ): ((IBinder) -> Unit)? { + if (!Flags.enableFullyImmersiveInDesktop()) return null + val displayLayout = displayController.getDisplayLayout(displayId) ?: return null + val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) ?: return null + val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return null + logV("Appending immersive exit for task: $immersiveTask in display: $displayId") + wct.setBounds(taskInfo.token, calculateMaximizeBounds(displayLayout, taskInfo)) + return { transition -> addPendingImmersiveExit(immersiveTask, displayId, transition) } + } + + /** + * Bring the given [taskInfo] out of immersive mode, if applicable. + * + * @param wct that will apply these changes + * @param taskInfo of the task that should exit immersive mode + * @return a function to apply once the transition that will apply these changes is started + */ + fun exitImmersiveIfApplicable( + wct: WindowContainerTransaction, + taskInfo: RunningTaskInfo + ): ((IBinder) -> Unit)? { + if (!Flags.enableFullyImmersiveInDesktop()) return null + if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { + // A full immersive task is being minimized, make sure the immersive state is broken + // (i.e. resize back to max bounds). + displayController.getDisplayLayout(taskInfo.displayId)?.let { displayLayout -> + wct.setBounds(taskInfo.token, calculateMaximizeBounds(displayLayout, taskInfo)) + logV("Appending immersive exit for task: ${taskInfo.taskId}") + return { transition -> + addPendingImmersiveExit( + taskId = taskInfo.taskId, + displayId = taskInfo.displayId, + transition = transition + ) + } + } + } + return null + } + + private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) { + pendingExternalExitTransitions.add( + ExternalPendingExit( + taskId = taskId, + displayId = displayId, + transition = transition + ) + ) + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback + ): Boolean { + val state = requireState() + if (transition != state.transition) return false + animateResize( + transitionState = state, + info = info, + startTransaction = startTransaction, + finishTransaction = finishTransaction, + finishCallback = finishCallback + ) + return true + } + + private fun animateResize( + transitionState: TransitionState, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback + ) { + val change = info.changes.first { c -> + val taskInfo = c.taskInfo + return@first taskInfo != null && taskInfo.taskId == transitionState.taskId + } + val leash = change.leash + val startBounds = change.startAbsBounds + val endBounds = change.endAbsBounds + + val updateTransaction = transactionSupplier() + ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds).apply { + duration = FULL_IMMERSIVE_ANIM_DURATION_MS + interpolator = DecelerateInterpolator() + addListener( + onStart = { + startTransaction + .setPosition(leash, startBounds.left.toFloat(), startBounds.top.toFloat()) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash) + onTaskResizeAnimationListener + ?.onAnimationStart(transitionState.taskId, startTransaction, startBounds) + ?: startTransaction.apply() + }, + onEnd = { + finishTransaction + .setPosition(leash, endBounds.left.toFloat(), endBounds.top.toFloat()) + .setWindowCrop(leash, endBounds.width(), endBounds.height()) + .apply() + onTaskResizeAnimationListener?.onAnimationEnd(transitionState.taskId) + finishCallback.onTransitionFinished(null /* wct */) + clearState() + } + ) + addUpdateListener { animation -> + val rect = animation.animatedValue as Rect + updateTransaction + .setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) + .setWindowCrop(leash, rect.width(), rect.height()) + .apply() + onTaskResizeAnimationListener + ?.onBoundsChange(transitionState.taskId, updateTransaction, rect) + ?: updateTransaction.apply() + } + start() + } + } + + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo + ): WindowContainerTransaction? = null + + override fun onTransitionConsumed( + transition: IBinder, + aborted: Boolean, + finishTransaction: SurfaceControl.Transaction? + ) { + val state = this.state ?: return + if (transition == state.transition && aborted) { + clearState() + } + super.onTransitionConsumed(transition, aborted, finishTransaction) + } + + /** + * Called when any transition in the system is ready to play. This is needed to update the + * repository state before window decorations are drawn (which happens immediately after + * |onTransitionReady|, before this transition actually animates) because drawing decorations + * depends on whether the task is in full immersive state or not. + */ + fun onTransitionReady(transition: IBinder, info: TransitionInfo) { + // Check if this is a pending external exit transition. + val pendingExit = pendingExternalExitTransitions + .firstOrNull { pendingExit -> pendingExit.transition == transition } + if (pendingExit != null) { + pendingExternalExitTransitions.remove(pendingExit) + if (info.hasTaskChange(taskId = pendingExit.taskId)) { + if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) { + logV("Pending external exit for task ${pendingExit.taskId} verified") + desktopRepository.setTaskInFullImmersiveState( + displayId = pendingExit.displayId, + taskId = pendingExit.taskId, + immersive = false + ) + } + } + return + } + + // Check if this is a direct immersive enter/exit transition. + val state = this.state ?: return + if (transition == state.transition) { + logV("Direct move for task ${state.taskId} in ${state.direction} direction verified") + when (state.direction) { + Direction.ENTER -> { + desktopRepository.setTaskInFullImmersiveState( + displayId = state.displayId, + taskId = state.taskId, + immersive = true + ) + } + Direction.EXIT -> { + desktopRepository.setTaskInFullImmersiveState( + displayId = state.displayId, + taskId = state.taskId, + immersive = false + ) + } + } + } + } + + private fun clearState() { + state = null + } + + private fun requireState(): TransitionState = + state ?: error("Expected non-null transition state") + + private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean = + changes.any { c -> c.taskInfo?.taskId == taskId } + + /** The state of the currently running transition. */ + private data class TransitionState( + val transition: IBinder, + val displayId: Int, + val taskId: Int, + val direction: Direction + ) + + /** + * Tracks state of a transition involving an immersive exit that is external to this class' own + * transitions. This usually means transitions that exit immersive mode as a side-effect and + * not the primary action (for example, minimizing the immersive task or launching a new task + * on top of the immersive task). + */ + data class ExternalPendingExit( + val taskId: Int, + val displayId: Int, + val transition: IBinder, + ) + + private enum class Direction { + ENTER, EXIT + } + + private fun logV(msg: String, vararg arguments: Any?) { + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + private companion object { + private const val TAG = "DesktopImmersive" + + private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt new file mode 100644 index 000000000000..435019929cbd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.app.ActivityTaskManager.INVALID_TASK_ID +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.Context +import android.os.Handler +import android.os.IBinder +import android.view.SurfaceControl +import android.view.WindowManager +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.freeform.FreeformTaskTransitionHandler +import com.android.wm.shell.freeform.FreeformTaskTransitionStarter +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.MixedTransitionHandler +import com.android.wm.shell.transition.Transitions + +/** The [Transitions.TransitionHandler] coordinates transition handlers in desktop windowing. */ +class DesktopMixedTransitionHandler( + private val context: Context, + private val transitions: Transitions, + private val desktopRepository: DesktopRepository, + private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler, + private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler, + private val interactionJankMonitor: InteractionJankMonitor, + @ShellMainThread private val handler: Handler, +) : MixedTransitionHandler, FreeformTaskTransitionStarter { + + /** Delegates starting transition to [FreeformTaskTransitionHandler]. */ + override fun startWindowingModeTransition( + targetWindowingMode: Int, + wct: WindowContainerTransaction?, + ) = freeformTaskTransitionHandler.startWindowingModeTransition(targetWindowingMode, wct) + + /** Delegates starting minimized mode transition to [FreeformTaskTransitionHandler]. */ + override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder = + freeformTaskTransitionHandler.startMinimizedModeTransition(wct) + + /** Starts close transition and handles or delegates desktop task close animation. */ + override fun startRemoveTransition(wct: WindowContainerTransaction?) { + requireNotNull(wct) + transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this) + } + + /** Returns null, as it only handles transitions started from Shell. */ + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo, + ): WindowContainerTransaction? = null + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback, + ): Boolean { + val closeChange = findCloseDesktopTaskChange(info) + if (closeChange == null) { + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: Should have closing desktop task", TAG) + return false + } + if (isLastDesktopTask(closeChange)) { + // Dispatch close desktop task animation to the default transition handlers. + return dispatchCloseLastDesktopTaskAnimation( + transition, + info, + closeChange, + startTransaction, + finishTransaction, + finishCallback, + ) + } + // Animate close desktop task transition with [CloseDesktopTaskTransitionHandler]. + return closeDesktopTaskTransitionHandler.startAnimation( + transition, + info, + startTransaction, + finishTransaction, + finishCallback, + ) + } + + /** + * Dispatch close desktop task animation to the default transition handlers. Allows delegating + * it to Launcher to animate in sync with show Home transition. + */ + private fun dispatchCloseLastDesktopTaskAnimation( + transition: IBinder, + info: TransitionInfo, + change: TransitionInfo.Change, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback, + ): Boolean { + // Starting the jank trace if closing the last window in desktop mode. + interactionJankMonitor.begin( + change.leash, + context, + handler, + CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE, + ) + // Dispatch the last desktop task closing animation. + return transitions.dispatchTransition( + transition, + info, + startTransaction, + finishTransaction, + { wct -> + // Finish the jank trace when closing the last window in desktop mode. + interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE) + finishCallback.onTransitionFinished(wct) + }, + /* skip= */ this + ) != null + } + + private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean = + change.taskInfo?.let { + desktopRepository.getActiveNonMinimizedTaskCount(it.displayId) == 1 + } ?: false + + private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? { + if (info.type != WindowManager.TRANSIT_CLOSE) return null + return info.changes.firstOrNull { change -> + change.mode == WindowManager.TRANSIT_CLOSE && + !change.hasFlags(TransitionInfo.FLAG_IS_WALLPAPER) && + change.taskInfo?.taskId != INVALID_TASK_ID && + change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM + } + } + + companion object { + private const val TAG = "DesktopMixedTransitionHandler" + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java index cca750014fc1..73e55b2c4792 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java @@ -36,7 +36,7 @@ public interface DesktopMode { * @param listener the listener to add. * @param callbackExecutor the executor to call the listener on. */ - void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener, + void addVisibleTasksListener(DesktopRepository.VisibleTasksListener listener, Executor callbackExecutor); /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 02cbe01d0a03..5a277316ffd4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog +import com.android.window.flags.Flags +import com.android.wm.shell.EventLogTags import com.android.wm.shell.protolog.ShellProtoLogGroup /** Event logger for logging desktop mode session events */ @@ -41,6 +43,7 @@ class DesktopModeEventLogger { /* exitReason */ 0, /* session_id */ sessionId ) + EventLogTags.writeWmShellEnterDesktopMode(enterReason.reason, sessionId) } /** @@ -61,6 +64,7 @@ class DesktopModeEventLogger { /* exitReason */ exitReason.reason, /* session_id */ sessionId ) + EventLogTags.writeWmShellExitDesktopMode(exitReason.reason, sessionId) } /** @@ -76,7 +80,8 @@ class DesktopModeEventLogger { ) logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED, - sessionId, taskUpdate) + sessionId, taskUpdate + ) } /** @@ -92,7 +97,8 @@ class DesktopModeEventLogger { ) logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED, - sessionId, taskUpdate) + sessionId, taskUpdate + ) } /** @@ -108,7 +114,46 @@ class DesktopModeEventLogger { ) logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED, - sessionId, taskUpdate) + sessionId, taskUpdate + ) + } + + /** + * Logs that a task resize event is starting with [taskSizeUpdate] within a + * Desktop mode [sessionId]. + */ + fun logTaskResizingStarted(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) { + if (!Flags.enableResizingMetrics()) return + + ProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s", + sessionId, + taskSizeUpdate.instanceId + ) + logTaskSizeUpdated( + FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE, + sessionId, taskSizeUpdate + ) + } + + /** + * Logs that a task resize event is ending with [taskSizeUpdate] within a + * Desktop mode [sessionId]. + */ + fun logTaskResizingEnded(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) { + if (!Flags.enableResizingMetrics()) return + + ProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s", + sessionId, + taskSizeUpdate.instanceId + ) + logTaskSizeUpdated( + FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE, + sessionId, taskSizeUpdate + ) } private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) { @@ -135,6 +180,56 @@ class DesktopModeEventLogger { /* visible_task_count */ taskUpdate.visibleTaskCount ) + EventLogTags.writeWmShellDesktopModeTaskUpdate( + /* task_event */ + taskEvent, + /* instance_id */ + taskUpdate.instanceId, + /* uid */ + taskUpdate.uid, + /* task_height */ + taskUpdate.taskHeight, + /* task_width */ + taskUpdate.taskWidth, + /* task_x */ + taskUpdate.taskX, + /* task_y */ + taskUpdate.taskY, + /* session_id */ + sessionId, + taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON, + taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON, + /* visible_task_count */ + taskUpdate.visibleTaskCount + ) + } + + private fun logTaskSizeUpdated( + resizingStage: Int, + sessionId: Int, + taskSizeUpdate: TaskSizeUpdate + ) { + FrameworkStatsLog.write( + DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID, + /* resize_trigger */ + taskSizeUpdate.resizeTrigger?.trigger ?: ResizeTrigger.UNKNOWN_RESIZE_TRIGGER.trigger, + /* resizing_stage */ + resizingStage, + /* input_method */ + taskSizeUpdate.inputMethod?.method ?: InputMethod.UNKNOWN_INPUT_METHOD.method, + /* desktop_mode_session_id */ + sessionId, + /* instance_id */ + taskSizeUpdate.instanceId, + /* uid */ + taskSizeUpdate.uid, + /* task_height */ + taskSizeUpdate.taskHeight, + /* task_width */ + taskSizeUpdate.taskWidth, + /* display_area */ + taskSizeUpdate.displayArea + ) } companion object { @@ -163,13 +258,35 @@ class DesktopModeEventLogger { val visibleTaskCount: Int, ) + /** + * Describes a task size update (resizing, snapping or maximizing to + * stable bounds). + * + * @property resizeTrigger the trigger for task resize + * @property inputMethod the input method for resizing this task + * @property instanceId instance id of the task + * @property uid uid of the app associated with the task + * @property taskHeight height of the task in dp + * @property taskWidth width of the task in dp + * @property displayArea the display size of the screen in dp + */ + data class TaskSizeUpdate( + val resizeTrigger: ResizeTrigger? = null, + val inputMethod: InputMethod? = null, + val instanceId: Int, + val uid: Int, + val taskHeight: Int, + val taskWidth: Int, + val displayArea: Int, + ) + // Default value used when the task was not minimized. @VisibleForTesting const val UNSET_MINIMIZE_REASON = FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE /** The reason a task was minimized. */ - enum class MinimizeReason (val reason: Int) { + enum class MinimizeReason(val reason: Int) { TASK_LIMIT( FrameworkStatsLog .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT @@ -186,7 +303,7 @@ class DesktopModeEventLogger { FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE /** The reason a task was unminimized. */ - enum class UnminimizeReason (val reason: Int) { + enum class UnminimizeReason(val reason: Int) { UNKNOWN( FrameworkStatsLog .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN @@ -250,8 +367,88 @@ class DesktopModeEventLogger { SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF) } + /** + * Enum ResizeTrigger mapped to the ResizeTrigger definition in + * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto + */ + enum class ResizeTrigger(val trigger: Int) { + UNKNOWN_RESIZE_TRIGGER( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER + ), + CORNER( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER + ), + EDGE( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__EDGE_RESIZE_TRIGGER + ), + TILING_DIVIDER( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__TILING_DIVIDER_RESIZE_TRIGGER + ), + MAXIMIZE_BUTTON( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_BUTTON_RESIZE_TRIGGER + ), + DOUBLE_TAP_APP_HEADER( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DOUBLE_TAP_APP_HEADER_RESIZE_TRIGGER + ), + DRAG_LEFT( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_LEFT_RESIZE_TRIGGER + ), + DRAG_RIGHT( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_RIGHT_RESIZE_TRIGGER + ), + SNAP_LEFT_MENU( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_LEFT_MENU_RESIZE_TRIGGER + ), + SNAP_RIGHT_MENU( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__SNAP_RIGHT_MENU_RESIZE_TRIGGER + ), + } + + /** + * Enum InputMethod mapped to the InputMethod definition in + * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto + */ + enum class InputMethod(val method: Int) { + UNKNOWN_INPUT_METHOD( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD + ), + TOUCH( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD + ), + STYLUS( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD + ), + MOUSE( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD + ), + TOUCHPAD( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCHPAD_INPUT_METHOD + ), + KEYBOARD( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__KEYBOARD_INPUT_METHOD + ), + } + private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE + private const val DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID = + FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 063747494a82..b8507e3b2764 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -68,9 +68,7 @@ class DesktopModeLoggerTransitionObserver( private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) } init { - if ( - Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context) - ) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(this::onInit, this) } } @@ -101,6 +99,9 @@ class DesktopModeLoggerTransitionObserver( fun onInit() { transitions.registerObserver(this) + SystemProperties.set( + VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, + VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE) } override fun onTransitionReady( @@ -441,5 +442,6 @@ class DesktopModeLoggerTransitionObserver( @VisibleForTesting const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY = "debug.tracing." + VISIBLE_TASKS_COUNTER_NAME + const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE = "0" } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index bd6172226cf2..6d4792250be2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -123,6 +123,29 @@ fun calculateInitialBounds( } /** + * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking + * resizability into consideration. + */ +fun calculateMaximizeBounds( + displayLayout: DisplayLayout, + taskInfo: RunningTaskInfo, +): Rect { + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + if (taskInfo.isResizeable) { + // if resizable then expand to entire stable bounds (full display minus insets) + return Rect(stableBounds) + } else { + // if non-resizable then calculate max bounds according to aspect ratio + val activityAspectRatio = calculateAspectRatio(taskInfo) + val newSize = maximizeSizeGivenAspectRatio(taskInfo, + Size(stableBounds.width(), stableBounds.height()), activityAspectRatio) + return centerInArea( + newSize, stableBounds, stableBounds.left, stableBounds.top) + } +} + +/** * Calculates the largest size that can fit in a given area while maintaining a specific aspect * ratio. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 72619195fb3f..52b92a89abdf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -21,6 +21,12 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; @@ -125,7 +131,7 @@ public class DesktopModeVisualIndicator { mContext = context; mTaskSurface = taskSurface; mRootTdaOrganizer = taskDisplayAreaOrganizer; - mCurrentType = IndicatorType.NO_INDICATOR; + mCurrentType = NO_INDICATOR; mDragStartState = dragStartState; } @@ -136,8 +142,16 @@ public class DesktopModeVisualIndicator { @NonNull IndicatorType updateIndicatorType(PointF inputCoordinates) { final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId); + // Perform a quick check first: any input off the left edge of the display should be split + // left, and split right for the right edge. This is universal across all drag event types. + if (inputCoordinates.x < 0) return TO_SPLIT_LEFT_INDICATOR; + if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR; // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone. - IndicatorType result = IndicatorType.NO_INDICATOR; + // In drags not originating on a freeform caption, we should default to a TO_DESKTOP + // indicator. + IndicatorType result = mDragStartState == DragStartState.FROM_FREEFORM + ? NO_INDICATOR + : TO_DESKTOP_INDICATOR; final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness); // Because drags in freeform use task position for indicator calculation, we need to @@ -149,10 +163,8 @@ public class DesktopModeVisualIndicator { captionHeight); final Region splitRightRegion = calculateSplitRightRegion(layout, transitionAreaWidth, captionHeight); - final Region toDesktopRegion = calculateToDesktopRegion(layout, splitLeftRegion, - splitRightRegion, fullscreenRegion); if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { - result = IndicatorType.TO_FULLSCREEN_INDICATOR; + result = TO_FULLSCREEN_INDICATOR; } if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_SPLIT_LEFT_INDICATOR; @@ -160,9 +172,6 @@ public class DesktopModeVisualIndicator { if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR; } - if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) { - result = IndicatorType.TO_DESKTOP_INDICATOR; - } if (mDragStartState != DragStartState.DRAGGED_INTENT) { transitionIndicator(result); } @@ -182,7 +191,7 @@ public class DesktopModeVisualIndicator { R.dimen.desktop_mode_fullscreen_region_scale); final float toFullscreenWidth = (layout.width() * toFullscreenScale); region.union(new Rect((int) ((layout.width() / 2f) - (toFullscreenWidth / 2f)), - -captionHeight, + Short.MIN_VALUE, (int) ((layout.width() / 2f) + (toFullscreenWidth / 2f)), transitionHeight)); } @@ -192,7 +201,7 @@ public class DesktopModeVisualIndicator { || mDragStartState == DragStartState.DRAGGED_INTENT ) { region.union(new Rect(0, - -captionHeight, + Short.MIN_VALUE, layout.width(), transitionHeight)); } @@ -200,21 +209,6 @@ public class DesktopModeVisualIndicator { } @VisibleForTesting - Region calculateToDesktopRegion(DisplayLayout layout, - Region splitLeftRegion, Region splitRightRegion, - Region toFullscreenRegion) { - final Region region = new Region(); - // If in desktop, we need no region. Otherwise it's the same for all windowing modes. - if (mDragStartState != DragStartState.FROM_FREEFORM) { - region.union(new Rect(0, 0, layout.width(), layout.height())); - region.op(splitLeftRegion, Region.Op.DIFFERENCE); - region.op(splitRightRegion, Region.Op.DIFFERENCE); - region.op(toFullscreenRegion, Region.Op.DIFFERENCE); - } - return region; - } - - @VisibleForTesting Region calculateSplitLeftRegion(DisplayLayout layout, int transitionEdgeWidth, int captionHeight) { final Region region = new Region(); @@ -311,7 +305,7 @@ public class DesktopModeVisualIndicator { } }); } - mCurrentType = IndicatorType.NO_INDICATOR; + mCurrentType = NO_INDICATOR; } /** @@ -322,9 +316,9 @@ public class DesktopModeVisualIndicator { if (mView == null) { createView(); } - if (mCurrentType == IndicatorType.NO_INDICATOR) { + if (mCurrentType == NO_INDICATOR) { fadeInIndicator(newType); - } else if (newType == IndicatorType.NO_INDICATOR) { + } else if (newType == NO_INDICATOR) { fadeOutIndicator(null /* finishCallback */); } else { final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 0e8c4e70e05d..5ac4ef5cf049 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -42,8 +42,8 @@ import java.util.function.Consumer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -/** Tracks task data for Desktop Mode. */ -class DesktopModeTaskRepository ( +/** Tracks desktop data for Android Desktop Windowing. */ +class DesktopRepository ( private val context: Context, shellInit: ShellInit, private val persistentRepository: DesktopPersistentRepository, @@ -60,6 +60,7 @@ class DesktopModeTaskRepository ( * @property minimizedTasks task ids for active freeform tasks that are currently minimized. * @property closingTasks task ids for tasks that are going to close, but are currently visible. * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom + * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode. * (top is at index 0). */ private data class DesktopTaskData( @@ -69,14 +70,24 @@ class DesktopModeTaskRepository ( // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver val closingTasks: ArraySet<Int> = ArraySet(), val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), + var fullImmersiveTaskId: Int? = null, ) { fun deepCopy(): DesktopTaskData = DesktopTaskData( activeTasks = ArraySet(activeTasks), visibleTasks = ArraySet(visibleTasks), minimizedTasks = ArraySet(minimizedTasks), closingTasks = ArraySet(closingTasks), - freeformTasksInZOrder = ArrayList(freeformTasksInZOrder) + freeformTasksInZOrder = ArrayList(freeformTasksInZOrder), + fullImmersiveTaskId = fullImmersiveTaskId ) + fun clear() { + activeTasks.clear() + visibleTasks.clear() + minimizedTasks.clear() + closingTasks.clear() + freeformTasksInZOrder.clear() + fullImmersiveTaskId = null + } } /* Current wallpaper activity token to remove wallpaper activity when last task is removed. */ @@ -300,6 +311,27 @@ class DesktopModeTaskRepository ( } } + /** Set whether the given task is the full-immersive task in this display. */ + fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) { + val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId) + if (immersive) { + desktopData.fullImmersiveTaskId = taskId + } else { + if (desktopData.fullImmersiveTaskId == taskId) { + desktopData.fullImmersiveTaskId = null + } + } + } + + /* Whether the task is in full-immersive state. */ + fun isTaskInFullImmersiveState(taskId: Int): Boolean { + return desktopTaskDataSequence().any { taskId == it.fullImmersiveTaskId } + } + + /** Returns the task that is currently in immersive mode in this display, or null. */ + fun getTaskInFullImmersiveState(displayId: Int): Int? = + desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId + private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) { visibleTasksListeners.forEach { (listener, executor) -> executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) } @@ -330,8 +362,17 @@ class DesktopModeTaskRepository ( /** Minimizes the task for [taskId] and [displayId] */ fun minimizeTask(displayId: Int, taskId: Int) { - logD("Minimize Task: display=%d, task=%d", displayId, taskId) - desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId) + if (displayId == INVALID_DISPLAY) { + // When a task vanishes it doesn't have a displayId. Find the display of the task and + // mark it as minimized. + getDisplayIdForTask(taskId)?.let { + minimizeTask(it, taskId) + } ?: logW("Minimize task: No display id found for task: taskId=%d", taskId) + } else { + logD("Minimize Task: display=%d, task=%d", displayId, taskId) + desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId) + } + if (Flags.enableDesktopWindowingPersistence()) { updatePersistentRepository(displayId) } @@ -385,6 +426,19 @@ class DesktopModeTaskRepository ( } /** + * Removes the desktop for the given [displayId] and returns the active tasks on that desktop. + */ + fun removeDesktop(displayId: Int): ArraySet<Int> { + if (!desktopTaskDataByDisplayId.contains(displayId)) { + logW("Could not find desktop to remove: displayId=%d", displayId) + return ArraySet() + } + val activeTasks = ArraySet(desktopTaskDataByDisplayId[displayId].activeTasks) + desktopTaskDataByDisplayId[displayId].clear() + return activeTasks + } + + /** * Updates active desktop gesture exclusion regions. * * If [desktopExclusionRegions] is accepted by [desktopGestureExclusionListener], updates it in @@ -438,10 +492,9 @@ class DesktopModeTaskRepository ( } } - internal fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " - pw.println("${prefix}DesktopModeTaskRepository") + pw.println("${prefix}DesktopRepository") dumpDesktopTaskData(pw, innerPrefix) pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}") pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}") @@ -483,10 +536,9 @@ class DesktopModeTaskRepository ( } companion object { - private const val TAG = "DesktopModeTaskRepository" + private const val TAG = "DesktopRepository" } } private fun <T> Iterable<T>.toDumpString(): String = joinToString(separator = ", ", prefix = "[", postfix = "]") - diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt new file mode 100644 index 000000000000..1ee2de958e55 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import com.android.wm.shell.freeform.TaskChangeListener + +/** Manages tasks handling specific to Android Desktop Mode. */ +class DesktopTaskChangeListener: TaskChangeListener { + + override fun onTaskOpening(taskInfo: RunningTaskInfo) { + // TODO: b/367268953 - Connect this with DesktopRepository. + } + + override fun onTaskChanging(taskInfo: RunningTaskInfo) { + // TODO: b/367268953 - Connect this with DesktopRepository. + } + + override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) { + // TODO: b/367268953 - Connect this with DesktopRepository. + } + + override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) { + // TODO: b/367268953 - Connect this with DesktopRepository. + } + + override fun onTaskClosing(taskInfo: RunningTaskInfo) { + // TODO: b/367268953 - Connect this with DesktopRepository. + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index afa27f9f1309..92535f37094a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.KeyguardManager @@ -47,6 +48,10 @@ import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_FRONT +import android.window.DesktopModeFlags +import android.window.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE +import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY +import android.window.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS import android.window.RemoteTransition import android.window.TransitionInfo import android.window.TransitionRequestInfo @@ -73,7 +78,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener +import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener @@ -86,10 +91,7 @@ import com.android.wm.shell.shared.ShellSharedConstants import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread -import android.window.flags.DesktopModeFlags -import android.window.flags.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE -import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY -import android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS +import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity @@ -101,6 +103,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTO import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.sysui.UserChangeListener import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility @@ -109,6 +112,7 @@ import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import com.android.wm.shell.windowdecor.extension.isFullscreen import com.android.wm.shell.windowdecor.extension.isMultiWindow +import com.android.wm.shell.windowdecor.extension.requestingImmersive import java.io.PrintWriter import java.util.Optional import java.util.concurrent.Executor @@ -133,7 +137,8 @@ class DesktopTasksController( private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, - private val taskRepository: DesktopModeTaskRepository, + private val immersiveTransitionHandler: DesktopFullImmersiveTransitionHandler, + private val taskRepository: DesktopRepository, private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver, private val launchAdjacentController: LaunchAdjacentController, private val recentsTransitionHandler: RecentsTransitionHandler, @@ -146,10 +151,12 @@ class DesktopTasksController( ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, - DragAndDropController.DragAndDropListener { + DragAndDropController.DragAndDropListener, + UserChangeListener { private val desktopMode: DesktopModeImpl private var visualIndicator: DesktopModeVisualIndicator? = null + private var userId: Int private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler = DesktopModeShellCommandHandler(this) private val mOnAnimationFinishedCallback = @@ -184,6 +191,7 @@ class DesktopTasksController( private var recentsAnimationRunning = false private lateinit var splitScreenController: SplitScreenController + lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter // Launch cookie used to identify a drag and drop transition to fullscreen after it has begun. // Used to prevent handleRequest from moving the new fullscreen task to freeform. private var dragAndDropFullscreenCookie: Binder? = null @@ -193,6 +201,7 @@ class DesktopTasksController( if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback({ onInit() }, this) } + userId = ActivityManager.getCurrentUser() } private fun onInit() { @@ -204,6 +213,7 @@ class DesktopTasksController( { createExternalInterface() }, this ) + shellController.addUserChangeListener(this) transitions.addHandler(this) dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener recentsTransitionHandler.addTransitionStateListener( @@ -226,6 +236,7 @@ class DesktopTasksController( toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener + immersiveTransitionHandler.onTaskResizeAnimationListener = listener } fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) { @@ -253,17 +264,12 @@ class DesktopTasksController( val wct = WindowContainerTransaction() bringDesktopAppsToFront(displayId, wct) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - val transitionType = transitionType(remoteTransition) - val handler = - remoteTransition?.let { - OneShotRemoteHandler(transitions.mainExecutor, remoteTransition) - } - transitions.startTransition(transitionType, wct, handler).also { t -> - handler?.setTransition(t) - } - } else { - shellTaskOrganizer.applyTransaction(wct) + val transitionType = transitionType(remoteTransition) + val handler = remoteTransition?.let { + OneShotRemoteHandler(transitions.mainExecutor, remoteTransition) + } + transitions.startTransition(transitionType, wct, handler).also { t -> + handler?.setTransition(t) } } @@ -350,6 +356,8 @@ class DesktopTasksController( // TODO(342378842): Instead of using default display, support multiple displays val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask( DEFAULT_DISPLAY, wct, taskId) + val runOnTransit = immersiveTransitionHandler + .exitImmersiveIfApplicable(wct, DEFAULT_DISPLAY) wct.startTask( taskId, ActivityOptions.makeBasic().apply { @@ -359,6 +367,7 @@ class DesktopTasksController( // TODO(343149901): Add DPI changes for task launch val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) addPendingMinimizeTransition(transition, taskToMinimize) + runOnTransit?.invoke(transition) return true } @@ -375,17 +384,15 @@ class DesktopTasksController( } logV("moveRunningTaskToDesktop taskId=%d", task.taskId) exitSplitIfApplicable(wct, task) + val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(wct, task.displayId) // Bring other apps to front first val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) addMoveToDesktopChanges(wct, task) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) - addPendingMinimizeTransition(transition, taskToMinimize) - } else { - shellTaskOrganizer.applyTransaction(wct) - } + val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) + addPendingMinimizeTransition(transition, taskToMinimize) + runOnTransit?.invoke(transition) } /** @@ -401,7 +408,7 @@ class DesktopTasksController( interactionJankMonitor.begin(taskSurface, context, handler, CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) dragToDesktopTransitionHandler.startDragToDesktopTransition( - taskInfo.taskId, + taskInfo, dragToDesktopValueAnimator ) } @@ -422,8 +429,13 @@ class DesktopTasksController( val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) + val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + wct, taskInfo.displayId) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) - transition?.let { addPendingMinimizeTransition(it, taskToMinimize) } + transition?.let { + addPendingMinimizeTransition(it, taskToMinimize) + runOnTransit?.invoke(transition) + } } /** @@ -453,20 +465,36 @@ class DesktopTasksController( removeWallpaperActivity(wct) } taskRepository.addClosingTask(displayId, taskId) + taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( + doesAnyTaskRequireTaskbarRounding( + displayId, + taskId + ) + ) } - /** - * Perform clean up of the desktop wallpaper activity if the minimized window task is the last - * active task. - * - * @param wct transaction to modify if the last active task is minimized - * @param taskId task id of the window that's being minimized - */ - fun onDesktopWindowMinimize(wct: WindowContainerTransaction, taskId: Int) { + fun minimizeTask(taskInfo: RunningTaskInfo) { + val taskId = taskInfo.taskId + val displayId = taskInfo.displayId + val wct = WindowContainerTransaction() if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) { + // Perform clean up of the desktop wallpaper activity if the minimized window task is + // the last active task. removeWallpaperActivity(wct) } - // Do not call taskRepository.minimizeTask because it will be called by DekstopTasksLimiter. + // Notify immersive handler as it might need to exit immersive state. + val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(wct, taskInfo) + + wct.reorder(taskInfo.token, false) + val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct) + desktopTasksLimiter.ifPresent { + it.addPendingMinimizeChange( + transition = transition, + displayId = displayId, + taskId = taskId + ) + } + runOnTransit?.invoke(transition) } /** Move a task with given `taskId` to fullscreen */ @@ -491,11 +519,9 @@ class DesktopTasksController( // Rather than set windowing mode to multi-window at task level, set it to // undefined and inherit from split stage. wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) - } else { - shellTaskOrganizer.applyTransaction(wct) - } + + transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + } private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) { @@ -529,36 +555,57 @@ class DesktopTasksController( val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - exitDesktopTaskTransitionHandler.startTransition( + exitDesktopTaskTransitionHandler.startTransition( transitionSource, wct, position, mOnAnimationFinishedCallback ) - } else { - shellTaskOrganizer.applyTransaction(wct) - releaseVisualIndicator() - } } /** Move a task to the front */ fun moveTaskToFront(taskId: Int) { - shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) } + val task = shellTaskOrganizer.getRunningTaskInfo(taskId) + if (task == null) moveBackgroundTaskToFront(taskId) else moveTaskToFront(task) + } + + /** + * Launch a background task in desktop. Note that this should be used when we are already in + * desktop. If outside of desktop and want to launch a background task in desktop, use + * [moveBackgroundTaskToDesktop] instead. + */ + private fun moveBackgroundTaskToFront(taskId: Int) { + logV("moveBackgroundTaskToFront taskId=%s", taskId) + val wct = WindowContainerTransaction() + // TODO: b/342378842 - Instead of using default display, support multiple displays + val taskToMinimize: RunningTaskInfo? = + addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId) + val runOnTransit = immersiveTransitionHandler + .exitImmersiveIfApplicable(wct, DEFAULT_DISPLAY) + wct.startTask( + taskId, + ActivityOptions.makeBasic().apply { + launchWindowingMode = WINDOWING_MODE_FREEFORM + }.toBundle(), + ) + val transition = transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */) + addPendingMinimizeTransition(transition, taskToMinimize) + runOnTransit?.invoke(transition) } /** Move a task to the front */ fun moveTaskToFront(taskInfo: RunningTaskInfo) { logV("moveTaskToFront taskId=%s", taskInfo.taskId) val wct = WindowContainerTransaction() - wct.reorder(taskInfo.token, true) - val taskToMinimize = addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */) - addPendingMinimizeTransition(transition, taskToMinimize) - } else { - shellTaskOrganizer.applyTransaction(wct) - } + wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */) + val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + wct, taskInfo.displayId) + val taskToMinimize = + addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo.taskId) + + val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */) + addPendingMinimizeTransition(transition, taskToMinimize) + runOnTransit?.invoke(transition) } /** @@ -614,13 +661,30 @@ class DesktopTasksController( val wct = WindowContainerTransaction() wct.reparent(task.token, displayAreaInfo.token, true /* onTop */) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + + transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + + } + + /** Moves a task in/out of full immersive state within the desktop. */ + fun toggleDesktopTaskFullImmersiveState(taskInfo: RunningTaskInfo) { + if (taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { + exitDesktopTaskFromFullImmersive(taskInfo) } else { - shellTaskOrganizer.applyTransaction(wct) + moveDesktopTaskToFullImmersive(taskInfo) } } + private fun moveDesktopTaskToFullImmersive(taskInfo: RunningTaskInfo) { + check(taskInfo.isFreeform) { "Task must already be in freeform" } + immersiveTransitionHandler.moveTaskToImmersive(taskInfo) + } + + private fun exitDesktopTaskFromFullImmersive(taskInfo: RunningTaskInfo) { + check(taskInfo.isFreeform) { "Task must already be in freeform" } + immersiveTransitionHandler.moveTaskToNonImmersive(taskInfo) + } + /** * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the stable * bounds) and a free floating state (either the last saved bounds if available or the default @@ -657,18 +721,7 @@ class DesktopTasksController( // and toggle to the stable bounds. taskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds) - if (taskInfo.isResizeable) { - // if resizable then expand to entire stable bounds (full display minus insets) - destinationBounds.set(stableBounds) - } else { - // if non-resizable then calculate max bounds according to aspect ratio - val activityAspectRatio = calculateAspectRatio(taskInfo) - val newSize = maximizeSizeGivenAspectRatio(taskInfo, - Size(stableBounds.width(), stableBounds.height()), activityAspectRatio) - val newBounds = centerInArea( - newSize, stableBounds, stableBounds.left, stableBounds.top) - destinationBounds.set(newBounds) - } + destinationBounds.set(calculateMaximizeBounds(displayLayout, taskInfo)) } @@ -684,10 +737,22 @@ class DesktopTasksController( taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding) val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - toggleResizeDesktopTaskTransitionHandler.startTransition(wct) + + toggleResizeDesktopTaskTransitionHandler.startTransition(wct) + + } + + private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect { + if (taskInfo.isResizeable) { + // if resizable then expand to entire stable bounds (full display minus insets) + return Rect(stableBounds) } else { - shellTaskOrganizer.applyTransaction(wct) + // if non-resizable then calculate max bounds according to aspect ratio + val activityAspectRatio = calculateAspectRatio(taskInfo) + val newSize = maximizeSizeGivenAspectRatio(taskInfo, + Size(stableBounds.width(), stableBounds.height()), activityAspectRatio) + return centerInArea( + newSize, stableBounds, stableBounds.left, stableBounds.top) } } @@ -784,11 +849,9 @@ class DesktopTasksController( taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true) val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds) - } else { - shellTaskOrganizer.applyTransaction(wct) - } + + toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds) + } @VisibleForTesting @@ -1246,9 +1309,11 @@ class DesktopTasksController( if (useDesktopOverrideDensity()) { wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE) } - // Desktop Mode is showing and we're launching a new Task - we might need to minimize - // a Task. - val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task) + // Desktop Mode is showing and we're launching a new Task: + // 1) Exit immersive if needed. + immersiveTransitionHandler.exitImmersiveIfApplicable(transition, wct, task.displayId) + // 2) minimize a Task if needed. + val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId) if (taskToMinimize != null) { addPendingMinimizeTransition(transition, taskToMinimize) return wct @@ -1274,8 +1339,12 @@ class DesktopTasksController( // Desktop Mode is already showing and we're launching a new Task - we might need to // minimize another Task. - val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task) + val taskToMinimize = + addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId) addPendingMinimizeTransition(transition, taskToMinimize) + immersiveTransitionHandler.exitImmersiveIfApplicable( + transition, wct, task.displayId + ) } } return null @@ -1307,14 +1376,11 @@ class DesktopTasksController( // Remove wallpaper activity when the last active task is removed removeWallpaperActivity(wct) } - taskRepository.addClosingTask(task.displayId, task.taskId) - // If a CLOSE is triggered on a desktop task, remove the task. - if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() && - taskRepository.isVisibleTask(task.taskId) && - transitionType == TRANSIT_CLOSE - ) { - wct.removeTask(task.token) + + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { + taskRepository.addClosingTask(task.displayId, task.taskId) } + taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding( task.displayId, @@ -1419,12 +1485,12 @@ class DesktopTasksController( private fun addAndGetMinimizeChangesIfNeeded( displayId: Int, wct: WindowContainerTransaction, - newTaskInfo: RunningTaskInfo + newTaskId: Int ): RunningTaskInfo? { if (!desktopTasksLimiter.isPresent) return null return desktopTasksLimiter .get() - .addAndGetMinimizeTaskChangesIfNeeded(displayId, wct, newTaskInfo) + .addAndGetMinimizeTaskChangesIfNeeded(displayId, wct, newTaskId) } private fun addPendingMinimizeTransition( @@ -1437,6 +1503,22 @@ class DesktopTasksController( } } + fun removeDesktop(displayId: Int) { + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return + + val tasksToRemove = taskRepository.removeDesktop(displayId) + val wct = WindowContainerTransaction() + tasksToRemove.forEach { + val task = shellTaskOrganizer.getRunningTaskInfo(it) + if (task != null) { + wct.removeTask(task.token) + } else { + recentTasksController?.removeBackgroundTask(it) + } + } + if (!wct.isEmpty) transitions.startTransition(TRANSIT_CLOSE, wct, null) + } + /** Enter split by using the focused desktop task in given `displayId`. */ fun enterSplit(displayId: Int, leftOrTop: Boolean) { getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) } @@ -1568,7 +1650,7 @@ class DesktopTasksController( return } - val indicator = visualIndicator ?: return + val indicator = getVisualIndicator() ?: return val indicatorType = indicator.updateIndicatorType( PointF(inputCoordinate.x, currentDragBounds.top.toFloat()), @@ -1779,6 +1861,22 @@ class DesktopTasksController( return true } + // TODO(b/366397912): Support full multi-user mode in Windowing. + override fun onUserChanged(newUserId: Int, userContext: Context) { + userId = newUserId + } + + /** Called when a task's info changes. */ + fun onTaskInfoChanged(taskInfo: RunningTaskInfo) { + if (!Flags.enableFullyImmersiveInDesktop()) return + val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId) + val requestingImmersive = taskInfo.requestingImmersive + if (inImmersive && !requestingImmersive) { + // Exit immersive if the app is no longer requesting it. + exitDesktopTaskFromFullImmersive(taskInfo) + } + } + private fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopTasksController") @@ -1948,6 +2046,12 @@ class DesktopTasksController( c.moveTaskToDesktop(taskId, transitionSource = transitionSource) } } + + override fun removeDesktop(displayId: Int) { + executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c -> + c.removeDesktop(displayId) + } + } } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index d84349b1ce1f..d6b721253abf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -24,6 +24,7 @@ import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_BACK import android.window.TransitionInfo import android.window.WindowContainerTransaction +import android.window.DesktopModeFlags import androidx.annotation.VisibleForTesting import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor @@ -38,12 +39,12 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver * Limits the number of tasks shown in Desktop Mode. * * This class should only be used if - * [android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] + * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] * is enabled and [maxTasksLimit] is strictly greater than 0. */ class DesktopTasksLimiter ( transitions: Transitions, - private val taskRepository: DesktopModeTaskRepository, + private val taskRepository: DesktopRepository, private val shellTaskOrganizer: ShellTaskOrganizer, private val maxTasksLimit: Int, private val interactionJankMonitor: InteractionJankMonitor, @@ -159,8 +160,10 @@ class DesktopTasksLimiter ( } @VisibleForTesting - inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener { + inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener { override fun onActiveTasksChanged(displayId: Int) { + // If back navigation is enabled, we shouldn't remove the leftover tasks + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return val wct = WindowContainerTransaction() removeLeftoverMinimizedTasks(displayId, wct) shellTaskOrganizer.applyTransaction(wct) @@ -208,15 +211,15 @@ class DesktopTasksLimiter ( fun addAndGetMinimizeTaskChangesIfNeeded( displayId: Int, wct: WindowContainerTransaction, - newFrontTaskInfo: RunningTaskInfo, + newFrontTaskId: Int, ): RunningTaskInfo? { ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d", - newFrontTaskInfo.taskId) + newFrontTaskId) val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront( taskRepository.getActiveNonMinimizedOrderedTasks(displayId), - newFrontTaskInfo.taskId) + newFrontTaskId) val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack) if (taskToMinimize != null) { wct.reorder(taskToMinimize.token, false /* onTop */) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 4796c4d0655a..0b1bb8f36fa8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -21,35 +21,37 @@ import android.content.Context import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager +import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_TO_BACK import android.window.TransitionInfo import android.window.WindowContainerTransaction -import android.window.flags.DesktopModeFlags -import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY +import android.window.DesktopModeFlags +import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions /** * A [Transitions.TransitionObserver] that observes shell transitions and updates the - * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop - * mode and other transitions that originate both within and outside shell. + * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop mode and + * other transitions that originate both within and outside shell. */ class DesktopTasksTransitionObserver( private val context: Context, - private val desktopModeTaskRepository: DesktopModeTaskRepository, + private val desktopRepository: DesktopRepository, private val transitions: Transitions, private val shellTaskOrganizer: ShellTaskOrganizer, shellInit: ShellInit ) : Transitions.TransitionObserver { + private var transitionToCloseWallpaper: IBinder? = null + init { - if ( - Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context) - ) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(::onInit, this) } } @@ -67,9 +69,26 @@ class DesktopTasksTransitionObserver( ) { // TODO: b/332682201 Update repository state updateWallpaperToken(info) - if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { handleBackNavigation(info) + removeTaskIfNeeded(info) + } + removeWallpaperOnLastTaskClosingIfNeeded(transition, info) + } + + private fun removeTaskIfNeeded(info: TransitionInfo) { + // Since we are no longer removing all the tasks [onTaskVanished], we need to remove them by + // checking the transitions. + if (!TransitionUtil.isOpeningType(info.type)) return + // Remove a task from the repository if the app is launched outside of desktop. + for (change in info.changes) { + val taskInfo = change.taskInfo + if (taskInfo == null || taskInfo.taskId == -1) continue + + if (desktopRepository.isActiveTask(taskInfo.taskId) && + taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) { + desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) + } } } @@ -83,16 +102,34 @@ class DesktopTasksTransitionObserver( continue } - if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 && + if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 && change.mode == TRANSIT_TO_BACK && - taskInfo.windowingMode == WINDOWING_MODE_FREEFORM - ) { - desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) } } } } + private fun removeWallpaperOnLastTaskClosingIfNeeded( + transition: IBinder, + info: TransitionInfo + ) { + for (change in info.changes) { + val taskInfo = change.taskInfo + if (taskInfo == null || taskInfo.taskId == -1) { + continue + } + + if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 && + change.mode == TRANSIT_CLOSE && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && + desktopRepository.wallpaperActivityToken != null) { + transitionToCloseWallpaper = transition + } + } + } + override fun onTransitionStarting(transition: IBinder) { // TODO: b/332682201 Update repository state } @@ -103,6 +140,16 @@ class DesktopTasksTransitionObserver( override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { // TODO: b/332682201 Update repository state + if (transitionToCloseWallpaper == transition) { + // TODO: b/362469671 - Handle merging the animation when desktop is also closing. + desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken -> + transitions.startTransition( + TRANSIT_CLOSE, + WindowContainerTransaction().removeTask(wallpaperActivityToken), + null) + } + transitionToCloseWallpaper = null + } } private fun updateWallpaperToken(info: TransitionInfo) { @@ -114,17 +161,16 @@ class DesktopTasksTransitionObserver( if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) { when (change.mode) { WindowManager.TRANSIT_OPEN -> { - desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token + desktopRepository.wallpaperActivityToken = taskInfo.token // After the task for the wallpaper is created, set it non-trimmable. // This is important to prevent recents from trimming and removing the // task. shellTaskOrganizer.applyTransaction( WindowContainerTransaction() - .setTaskTrimmableFromRecents(taskInfo.token, false) - ) + .setTaskTrimmableFromRecents(taskInfo.token, false)) } - WindowManager.TRANSIT_CLOSE -> - desktopModeTaskRepository.wallpaperActivityToken = null + TRANSIT_CLOSE -> + desktopRepository.wallpaperActivityToken = null else -> {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 2bc01b2f310e..8e264b2410f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -109,8 +109,8 @@ sealed class DragToDesktopTransitionHandler( * after one of the "end" or "cancel" transitions is merged into this transition. */ fun startDragToDesktopTransition( - taskId: Int, - dragToDesktopAnimator: MoveToDesktopAnimator, + taskInfo: RunningTaskInfo, + dragToDesktopAnimator: MoveToDesktopAnimator ) { if (inProgress) { ProtoLog.v( @@ -137,23 +137,26 @@ sealed class DragToDesktopTransitionHandler( ) val wct = WindowContainerTransaction() wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle()) + // The home launch done above will result in an attempt to move the task to pip if + // applicable, resulting in a broken state. Prevent that here. + wct.setDoNotPip(taskInfo.token) val startTransitionToken = transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this) transitionState = - if (isSplitTask(taskId)) { + if (isSplitTask(taskInfo.taskId)) { val otherTask = - getOtherSplitTask(taskId) + getOtherSplitTask(taskInfo.taskId) ?: throw IllegalStateException("Expected split task to have a counterpart.") TransitionState.FromSplit( - draggedTaskId = taskId, + draggedTaskId = taskInfo.taskId, dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, otherSplitTask = otherTask ) } else { TransitionState.FromFullscreen( - draggedTaskId = taskId, + draggedTaskId = taskInfo.taskId, dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index b036e40e6e16..86351e364cdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -49,4 +49,7 @@ interface IDesktopMode { /** Move a task with given `taskId` to desktop */ void moveToDesktop(int taskId, in DesktopModeTransitionSource transitionSource); + + /** Remove desktop on the given display */ + oneway void removeDesktop(int displayId); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt index 6013e97977d8..334dc5aca19d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt @@ -16,98 +16,341 @@ package com.android.wm.shell.desktopmode.education -import android.app.ActivityManager.RunningTaskInfo -import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.annotation.DimenRes +import android.annotation.StringRes +import android.content.Context +import android.content.res.Resources +import android.graphics.Point import android.os.SystemProperties +import android.util.Slog +import androidx.core.content.withStyledAttributes import com.android.window.flags.Flags -import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.R +import com.android.wm.shell.desktopmode.CaptionState +import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository +import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode +import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource +import com.android.wm.shell.windowdecor.common.DecorThemeUtil +import com.android.wm.shell.windowdecor.common.Theme +import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController +import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.EducationViewConfig +import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.MainCoroutineDispatcher +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.launch /** * Controls app handle education end to end. * * Listen to the user trigger for app handle education, calls an api to check if the education - * should be shown and calls an api to show education. + * should be shown and controls education UI. */ @OptIn(kotlinx.coroutines.FlowPreview::class) @kotlinx.coroutines.ExperimentalCoroutinesApi class AppHandleEducationController( + private val context: Context, private val appHandleEducationFilter: AppHandleEducationFilter, - shellTaskOrganizer: ShellTaskOrganizer, private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository, - @ShellMainThread private val applicationCoroutineScope: CoroutineScope + private val windowDecorCaptionHandleRepository: WindowDecorCaptionHandleRepository, + private val windowingEducationViewController: DesktopWindowingEducationTooltipController, + @ShellMainThread private val applicationCoroutineScope: CoroutineScope, + @ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher, ) { + private val decorThemeUtil = DecorThemeUtil(context) + private lateinit var openHandleMenuCallback: (Int) -> Unit + private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit + init { runIfEducationFeatureEnabled { - // TODO: b/361038716 - Use app handle state flow instead of focus task change flow - val focusTaskChangeFlow = focusTaskChangeFlow(shellTaskOrganizer) applicationCoroutineScope.launch { - // Central block handling the app's educational flow end-to-end. - // This flow listens to the changes to the result of - // [WindowingEducationProto#hasEducationViewedTimestampMillis()] in datastore proto object + // Central block handling the app handle's educational flow end-to-end. isEducationViewedFlow() .flatMapLatest { isEducationViewed -> if (isEducationViewed) { // If the education is viewed then return emptyFlow() that completes immediately. - // This will help us to not listen to focus task changes after the education has - // been viewed already. + // This will help us to not listen to [captionHandleStateFlow] after the education + // has been viewed already. emptyFlow() } else { - // This flow listens for focus task changes, which trigger the app handle education. - focusTaskChangeFlow - .filter { runningTaskInfo -> - runningTaskInfo.topActivityInfo?.packageName?.let { - appHandleEducationFilter.shouldShowAppHandleEducation(it) - } ?: false && runningTaskInfo.windowingMode != WINDOWING_MODE_FREEFORM + // Listen for changes to window decor's caption handle. + windowDecorCaptionHandleRepository.captionStateFlow + // Wait for few seconds before emitting the latest state. + .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) + .filter { captionState -> + captionState is CaptionState.AppHandle && + appHandleEducationFilter.shouldShowAppHandleEducation(captionState) } - .distinctUntilChanged() } } - .debounce( - APP_HANDLE_EDUCATION_DELAY) // Wait for few seconds, if the focus task changes. - // During the delay then current emission will be cancelled. - .flowOn(Dispatchers.IO) - .collectLatest { - // Fire and forget show education suspend function, manage entire lifecycle of - // tooltip in UI class. + .flowOn(backgroundDispatcher) + .collectLatest { captionState -> + val tooltipColorScheme = tooltipColorScheme(captionState) + + showEducation(captionState, tooltipColorScheme) + // After showing first tooltip, mark education as viewed + appHandleEducationDatastoreRepository.updateEducationViewedTimestampMillis(true) + } + } + + applicationCoroutineScope.launch { + if (isFeatureUsed()) return@launch + windowDecorCaptionHandleRepository.captionStateFlow + .filter { captionState -> + captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded + } + .take(1) + .flowOn(backgroundDispatcher) + .collect { + // If user expands app handle, mark user has used the feature + appHandleEducationDatastoreRepository.updateFeatureUsedTimestampMillis(true) } } } } private inline fun runIfEducationFeatureEnabled(block: () -> Unit) { - if (Flags.enableDesktopWindowingAppHandleEducation()) block() + if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation()) block() } + private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) { + val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds + val tooltipGlobalCoordinates = + Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom) + // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the cue. + // Populate information important to inflate app handle education tooltip. + val appHandleTooltipConfig = + EducationViewConfig( + tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip, + tooltipColorScheme = tooltipColorScheme, + tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, + tooltipText = getString(R.string.windowing_app_handle_education_tooltip), + arrowDirection = DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP, + onEducationClickAction = { + launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) } + openHandleMenuCallback(captionState.runningTaskInfo.taskId) + }, + onDismissAction = { + launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) } + }, + ) + + windowingEducationViewController.showEducationTooltip( + tooltipViewConfig = appHandleTooltipConfig, taskId = captionState.runningTaskInfo.taskId) + } + + /** Show tooltip that points to windowing image button in app handle menu */ + private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) { + val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height) + val windowingOptionPillHeight = getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height) + val appHandleMenuWidth = + getSize(R.dimen.desktop_mode_handle_menu_width) + + getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin) + val appHandleMenuMargins = + getSize(R.dimen.desktop_mode_handle_menu_margin_top) + + getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin) + + windowDecorCaptionHandleRepository.captionStateFlow + // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu + // has been expanded. + .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds) + .catchTimeoutAndLog { + // TODO: b/341320146 - Log previous tooltip was dismissed + } + // Wait for few milliseconds before emitting the latest state. + .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) + .filter { captionState -> + // Filter out states when app handle is not visible or not expanded. + captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded + } + // Before showing this tooltip, stop listening to further emissions to avoid accidentally + // showing the same tooltip on future emissions. + .take(1) + .flowOn(backgroundDispatcher) + .collectLatest { captionState -> + captionState as CaptionState.AppHandle + val appHandleBounds = captionState.globalAppHandleBounds + val tooltipGlobalCoordinates = + Point( + appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2, + appHandleBounds.top + + appHandleMenuMargins + + appInfoPillHeight + + windowingOptionPillHeight / 2) + // Populate information important to inflate windowing image button education tooltip. + val windowingImageButtonTooltipConfig = + EducationViewConfig( + tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip, + tooltipColorScheme = tooltipColorScheme, + tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, + tooltipText = + getString(R.string.windowing_desktop_mode_image_button_education_tooltip), + arrowDirection = + DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT, + onEducationClickAction = { + launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) } + toDesktopModeCallback( + captionState.runningTaskInfo.taskId, + DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON) + }, + onDismissAction = { + launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) } + }, + ) + + windowingEducationViewController.showEducationTooltip( + taskId = captionState.runningTaskInfo.taskId, + tooltipViewConfig = windowingImageButtonTooltipConfig) + } + } + + /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */ + private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) { + windowDecorCaptionHandleRepository.captionStateFlow + // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered + // desktop mode. + .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds) + .catchTimeoutAndLog { + // TODO: b/341320146 - Log previous tooltip was dismissed + } + // Wait for few milliseconds before emitting the latest state. + .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) + .filter { captionState -> + // Filter out states when app header is not visible or expanded. + captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded + } + // Before showing this tooltip, stop listening to further emissions to avoid accidentally + // showing the same tooltip on future emissions. + .take(1) + .flowOn(backgroundDispatcher) + .collectLatest { captionState -> + captionState as CaptionState.AppHeader + val globalAppChipBounds = captionState.globalAppChipBounds + val tooltipGlobalCoordinates = + Point( + globalAppChipBounds.right, + globalAppChipBounds.top + globalAppChipBounds.height() / 2) + // Populate information important to inflate exit desktop mode education tooltip. + val exitWindowingTooltipConfig = + EducationViewConfig( + tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip, + tooltipColorScheme = tooltipColorScheme, + tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, + tooltipText = getString(R.string.windowing_desktop_mode_exit_education_tooltip), + arrowDirection = + DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT, + onDismissAction = {}, + onEducationClickAction = { + openHandleMenuCallback(captionState.runningTaskInfo.taskId) + }, + ) + windowingEducationViewController.showEducationTooltip( + taskId = captionState.runningTaskInfo.taskId, + tooltipViewConfig = exitWindowingTooltipConfig, + ) + } + } + + private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme { + context.withStyledAttributes( + set = null, + attrs = + intArrayOf( + com.android.internal.R.attr.materialColorOnTertiaryFixed, + com.android.internal.R.attr.materialColorTertiaryFixed, + com.android.internal.R.attr.materialColorTertiaryFixedDim), + defStyleAttr = 0, + defStyleRes = 0) { + val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0) + val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0) + val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0) + val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo + + val tooltipContainerColor = + if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) { + tertiaryFixed + } else { + tertiaryFixedDim + } + return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed) + } + return TooltipColorScheme(0, 0, 0) + } + + /** + * Setup callbacks for app handle education tooltips. + * + * @param openHandleMenuCallback callback invoked to open app handle menu or app chip menu. + * @param toDesktopModeCallback callback invoked to move task into desktop mode. + */ + fun setAppHandleEducationTooltipCallbacks( + openHandleMenuCallback: (taskId: Int) -> Unit, + toDesktopModeCallback: (taskId: Int, DesktopModeTransitionSource) -> Unit + ) { + this.openHandleMenuCallback = openHandleMenuCallback + this.toDesktopModeCallback = toDesktopModeCallback + } + + private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) = + catch { exception -> + if (exception is TimeoutCancellationException) block() else throw exception + } + + private fun launchWithExceptionHandling(block: suspend () -> Unit) = + applicationCoroutineScope.launch { + try { + block() + } catch (e: Throwable) { + Slog.e(TAG, "Error: ", e) + } + } + + /** + * Listens to the changes to [WindowingEducationProto#hasEducationViewedTimestampMillis()] in + * datastore proto object. + */ private fun isEducationViewedFlow(): Flow<Boolean> = appHandleEducationDatastoreRepository.dataStoreFlow .map { preferences -> preferences.hasEducationViewedTimestampMillis() } .distinctUntilChanged() - private fun focusTaskChangeFlow(shellTaskOrganizer: ShellTaskOrganizer): Flow<RunningTaskInfo> = - callbackFlow { - val focusTaskChange = ShellTaskOrganizer.FocusListener { taskInfo -> trySend(taskInfo) } - shellTaskOrganizer.addFocusListener(focusTaskChange) - awaitClose { shellTaskOrganizer.removeFocusListener(focusTaskChange) } - } + /** + * Listens to the changes to [WindowingEducationProto#hasFeatureUsedTimestampMillis()] in + * datastore proto object. + */ + private suspend fun isFeatureUsed(): Boolean = + appHandleEducationDatastoreRepository.dataStoreFlow.first().hasFeatureUsedTimestampMillis() - private companion object { - val APP_HANDLE_EDUCATION_DELAY: Long + private fun getSize(@DimenRes resourceId: Int): Int { + if (resourceId == Resources.ID_NULL) return 0 + return context.resources.getDimensionPixelSize(resourceId) + } + + private fun getString(@StringRes resId: Int): String = context.resources.getString(resId) + + companion object { + const val TAG = "AppHandleEducationController" + val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L) + + val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long + get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt index 51bdb40e12e6..15f4c249cf22 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt @@ -22,6 +22,7 @@ import android.content.Context import android.os.SystemClock import android.provider.Settings.Secure import com.android.wm.shell.R +import com.android.wm.shell.desktopmode.CaptionState import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto import java.time.Duration @@ -35,8 +36,12 @@ class AppHandleEducationFilter( context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager /** Returns true if conditions to show app handle education are met, returns false otherwise. */ - suspend fun shouldShowAppHandleEducation(focusAppPackageName: String): Boolean { + suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean { + if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false + val focusAppPackageName = + captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false val windowingEducationProto = appHandleEducationDatastoreRepository.windowingEducationProto() + return isFocusAppInAllowlist(focusAppPackageName) && !isOtherEducationShowing() && hasSufficientTimeSinceSetup() && diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt index f420c5be456f..d21b208df482 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt @@ -71,6 +71,37 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) { suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first() /** + * Updates [WindowingEducationProto.educationViewedTimestampMillis_] field in datastore with + * current timestamp if [isViewed] is true, if not then clears the field. + */ + suspend fun updateEducationViewedTimestampMillis(isViewed: Boolean) { + dataStore.updateData { preferences -> + if (isViewed) { + preferences + .toBuilder() + .setEducationViewedTimestampMillis(System.currentTimeMillis()) + .build() + } else { + preferences.toBuilder().clearEducationViewedTimestampMillis().build() + } + } + } + + /** + * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current + * timestamp if [isViewed] is true, if not then clears the field. + */ + suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) { + dataStore.updateData { preferences -> + if (isViewed) { + preferences.toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build() + } else { + preferences.toBuilder().clearFeatureUsedTimestampMillis().build() + } + } + } + + /** * Updates [AppHandleEducation.appUsageStats] and * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with * [appUsageStats] and [appUsageStatsLastUpdateTimestamp]. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java index eee5aaee3ec3..3379ff2a8c30 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java @@ -35,6 +35,7 @@ public class FreeformComponents { public final ShellTaskOrganizer.TaskListener mTaskListener; public final Optional<Transitions.TransitionHandler> mTransitionHandler; public final Optional<Transitions.TransitionObserver> mTransitionObserver; + public final Optional<FreeformTaskTransitionStarterInitializer> mTransitionStarterInitializer; /** * Creates an instance with the given components. @@ -42,10 +43,12 @@ public class FreeformComponents { public FreeformComponents( ShellTaskOrganizer.TaskListener taskListener, Optional<Transitions.TransitionHandler> transitionHandler, - Optional<Transitions.TransitionObserver> transitionObserver) { + Optional<Transitions.TransitionObserver> transitionObserver, + Optional<FreeformTaskTransitionStarterInitializer> transitionStarterInitializer) { mTaskListener = taskListener; mTransitionHandler = transitionHandler; mTransitionObserver = transitionObserver; + mTransitionStarterInitializer = transitionStarterInitializer; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 83cc18baf6cc..a16446fffa15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -24,11 +24,13 @@ import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.util.SparseArray; import android.view.SurfaceControl; +import android.window.DesktopModeFlags; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.LaunchAdjacentController; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; @@ -48,7 +50,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, private final Context mContext; private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository; + private final Optional<DesktopRepository> mDesktopRepository; + private final Optional<DesktopTasksController> mDesktopTasksController; private final WindowDecorViewModel mWindowDecorationViewModel; private final LaunchAdjacentController mLaunchAdjacentController; @@ -63,13 +66,15 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<DesktopModeTaskRepository> desktopModeTaskRepository, + Optional<DesktopRepository> desktopRepository, + Optional<DesktopTasksController> desktopTasksController, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorationViewModel) { mContext = context; mShellTaskOrganizer = shellTaskOrganizer; mWindowDecorationViewModel = windowDecorationViewModel; - mDesktopModeTaskRepository = desktopModeTaskRepository; + mDesktopRepository = desktopRepository; + mDesktopTasksController = desktopTasksController; mLaunchAdjacentController = launchAdjacentController; if (shellInit != null) { shellInit.addInitCallback(this::onInit, this); @@ -94,14 +99,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, state.mTaskInfo = taskInfo; state.mLeash = leash; mTasks.put(taskInfo.taskId, state); - if (!Transitions.ENABLE_SHELL_TRANSITIONS) { - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mWindowDecorationViewModel.onTaskOpening(taskInfo, leash, t, t); - t.apply(); - } if (DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopModeTaskRepository.ifPresent(repository -> { + mDesktopRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId); if (taskInfo.isVisible) { repository.addActiveTask(taskInfo.displayId, taskInfo.taskId); @@ -120,14 +120,21 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mTasks.remove(taskInfo.taskId); if (DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopModeTaskRepository.ifPresent(repository -> { - repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); + mDesktopRepository.ifPresent(repository -> { + // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() + || repository.isClosingTask(taskInfo.taskId)) { + // A task that's vanishing should be removed: + // - If it's closed by the X button which means it's marked as a closing task. + repository.removeClosingTask(taskInfo.taskId); + repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); + } else { + repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId, false); + repository.minimizeTask(taskInfo.displayId, taskInfo.taskId); + } }); } mWindowDecorationViewModel.onTaskVanished(taskInfo); - if (!Transitions.ENABLE_SHELL_TRANSITIONS) { - mWindowDecorationViewModel.destroyWindowDecoration(taskInfo); - } updateLaunchAdjacentController(); } @@ -137,14 +144,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d", taskInfo.taskId); + mDesktopTasksController.ifPresent(c -> c.onTaskInfoChanged(taskInfo)); mWindowDecorationViewModel.onTaskInfoChanged(taskInfo); state.mTaskInfo = taskInfo; if (DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopModeTaskRepository.ifPresent(repository -> { + mDesktopRepository.ifPresent(repository -> { if (taskInfo.isVisible) { repository.addActiveTask(taskInfo.displayId, taskInfo.taskId); - } else if (repository.isClosingTask(taskInfo.taskId)) { - repository.removeClosingTask(taskInfo.taskId); } repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); @@ -172,7 +178,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, "Freeform Task Focus Changed: #%d focused=%b", taskInfo.taskId, taskInfo.isFocused); if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) { - mDesktopModeTaskRepository.ifPresent(repository -> { + mDesktopRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 517e20910f6d..6aaf001d46f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -19,16 +19,12 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.WindowConfiguration; -import android.content.Context; import android.graphics.Rect; -import android.os.Handler; import android.os.IBinder; import android.util.ArrayMap; import android.view.SurfaceControl; @@ -40,14 +36,9 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; -import com.android.wm.shell.shared.annotations.ShellMainThread; -import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; -import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.util.ArrayList; import java.util.List; @@ -59,48 +50,24 @@ import java.util.List; public class FreeformTaskTransitionHandler implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { private static final int CLOSE_ANIM_DURATION = 400; - private final Context mContext; private final Transitions mTransitions; - private final WindowDecorViewModel mWindowDecorViewModel; - private final DesktopModeTaskRepository mDesktopModeTaskRepository; private final DisplayController mDisplayController; - private final InteractionJankMonitor mInteractionJankMonitor; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; - @ShellMainThread - private final Handler mHandler; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); public FreeformTaskTransitionHandler( - ShellInit shellInit, Transitions transitions, - Context context, - WindowDecorViewModel windowDecorViewModel, DisplayController displayController, ShellExecutor mainExecutor, - ShellExecutor animExecutor, - DesktopModeTaskRepository desktopModeTaskRepository, - InteractionJankMonitor interactionJankMonitor, - @ShellMainThread Handler handler) { + ShellExecutor animExecutor) { mTransitions = transitions; - mContext = context; - mWindowDecorViewModel = windowDecorViewModel; - mDesktopModeTaskRepository = desktopModeTaskRepository; mDisplayController = displayController; - mInteractionJankMonitor = interactionJankMonitor; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; - mHandler = handler; - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - shellInit.addInitCallback(this::onInit, this); - } - } - - private void onInit() { - mWindowDecorViewModel.setFreeformTaskTransitionStarter(this); } @Override @@ -269,20 +236,12 @@ public class FreeformTaskTransitionHandler startBounds.top + (animation.getAnimatedFraction() * screenHeight)); t.apply(); }); - if (mDesktopModeTaskRepository.getActiveNonMinimizedTaskCount( - change.getTaskInfo().displayId) == 1) { - // Starting the jank trace if closing the last window in desktop mode. - mInteractionJankMonitor.begin( - sc, mContext, mHandler, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); - } animator.addListener( new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { animations.remove(animator); onAnimFinish.run(); - mInteractionJankMonitor.end( - CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); } }); animations.add(animator); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java index ffcc526eac40..771573d48e45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -27,7 +27,10 @@ import android.window.WindowContainerToken; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.window.flags.Flags; +import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -36,6 +39,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; /** * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes, @@ -44,7 +48,10 @@ import java.util.Map; */ public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver { private final Transitions mTransitions; + private final Optional<DesktopFullImmersiveTransitionHandler> mImmersiveTransitionHandler; private final WindowDecorViewModel mWindowDecorViewModel; + private final Optional<TaskChangeListener> mTaskChangeListener; + private final FocusTransitionObserver mFocusTransitionObserver; private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo = new HashMap<>(); @@ -53,10 +60,16 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs Context context, ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { + Optional<DesktopFullImmersiveTransitionHandler> immersiveTransitionHandler, + WindowDecorViewModel windowDecorViewModel, + Optional<TaskChangeListener> taskChangeListener, + FocusTransitionObserver focusTransitionObserver) { mTransitions = transitions; + mImmersiveTransitionHandler = immersiveTransitionHandler; mWindowDecorViewModel = windowDecorViewModel; - if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) { + mTaskChangeListener = taskChangeListener; + mFocusTransitionObserver = focusTransitionObserver; + if (FreeformComponents.isFreeformEnabled(context)) { shellInit.addInitCallback(this::onInit, this); } } @@ -72,6 +85,16 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT) { + if (Flags.enableFullyImmersiveInDesktop()) { + // TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository + // is updated from there **before** the |mWindowDecorViewModel| methods are invoked. + // Otherwise window decoration relayout won't run with the immersive state up to date. + mImmersiveTransitionHandler.ifPresent(h -> h.onTransitionReady(transition, info)); + } + // Update focus state first to ensure the correct state can be queried from listeners. + // TODO(371503964): Remove this once the unified task repository is ready. + mFocusTransitionObserver.updateFocusState(info); + final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>(); final ArrayList<WindowContainerToken> taskParents = new ArrayList<>(); for (TransitionInfo.Change change : info.getChanges()) { @@ -103,6 +126,9 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs case WindowManager.TRANSIT_TO_FRONT: onToFrontTransitionReady(change, startT, finishT); break; + case WindowManager.TRANSIT_TO_BACK: + onToBackTransitionReady(change, startT, finishT); + break; case WindowManager.TRANSIT_CLOSE: { taskInfoList.add(change.getTaskInfo()); onCloseTransitionReady(change, startT, finishT); @@ -120,29 +146,49 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + mTaskChangeListener.ifPresent( + listener -> listener.onTaskOpening(change.getTaskInfo())); mWindowDecorViewModel.onTaskOpening( - change.getTaskInfo(), change.getLeash(), startT, finishT); + change.getTaskInfo(), change.getLeash(), startT, finishT); } private void onCloseTransitionReady( TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + mTaskChangeListener.ifPresent( + listener -> listener.onTaskClosing(change.getTaskInfo())); mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT); + } private void onChangeTransitionReady( TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + mTaskChangeListener.ifPresent(listener -> + listener.onTaskChanging(change.getTaskInfo())); mWindowDecorViewModel.onTaskChanging( change.getTaskInfo(), change.getLeash(), startT, finishT); } + private void onToFrontTransitionReady( TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + mTaskChangeListener.ifPresent( + listener -> listener.onTaskMovingToFront(change.getTaskInfo())); + mWindowDecorViewModel.onTaskChanging( + change.getTaskInfo(), change.getLeash(), startT, finishT); + } + + private void onToBackTransitionReady( + TransitionInfo.Change change, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + mTaskChangeListener.ifPresent( + listener -> listener.onTaskMovingToBack(change.getTaskInfo())); mWindowDecorViewModel.onTaskChanging( change.getTaskInfo(), change.getLeash(), startT, finishT); } @@ -179,4 +225,4 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i)); } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt new file mode 100644 index 000000000000..98bdf059e738 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarterInitializer.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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.freeform + +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.windowdecor.WindowDecorViewModel + +/** + * Sets up [FreeformTaskTransitionStarter] for [WindowDecorViewModel] when shell finishes + * initializing. + * + * Used to extract the setup logic from the starter implementation. + */ +class FreeformTaskTransitionStarterInitializer( + shellInit: ShellInit, + private val windowDecorViewModel: WindowDecorViewModel, + private val freeformTaskTransitionStarter: FreeformTaskTransitionStarter +) { + init { + shellInit.addInitCallback(::onShellInit, this) + } + + /** Sets up [WindowDecorViewModel] transition starter with [FreeformTaskTransitionStarter] */ + private fun onShellInit() { + windowDecorViewModel.setFreeformTaskTransitionStarter(freeformTaskTransitionStarter) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt new file mode 100644 index 000000000000..f07c069bb420 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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.freeform + +import android.app.ActivityManager.RunningTaskInfo; + +/** + * Interface used by [FreeformTaskTransitionObserver] to manage freeform tasks. + * + * The implementations are responsible for handle all the task management. + */ +interface TaskChangeListener { + /** Notifies a task opening in freeform mode. */ + fun onTaskOpening(taskInfo: RunningTaskInfo) + + /** Notifies a task info update on the given task. */ + fun onTaskChanging(taskInfo: RunningTaskInfo) + + /** Notifies a task moving to the front. */ + fun onTaskMovingToFront(taskInfo: RunningTaskInfo) + + /** Notifies a task moving to the back. */ + fun onTaskMovingToBack(taskInfo: RunningTaskInfo) + + /** Notifies a task is closing. */ + fun onTaskClosing(taskInfo: RunningTaskInfo) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index abec3b9c0c3b..f8d2011d0934 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -28,6 +28,8 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; import static android.view.WindowManager.TRANSIT_SLEEP; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -44,6 +46,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; +import android.window.KeyguardState; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; @@ -388,5 +391,18 @@ public class KeyguardTransitionHandler mMainExecutor.execute(() -> mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen); } + + @Override + public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final KeyguardState keyguardState = + new KeyguardState.Builder(android.view.Display.DEFAULT_DISPLAY) + .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build(); + wct.addKeyguardState(keyguardState); + mMainExecutor.execute(() -> { + mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, + wct, KeyguardTransitionHandler.this); + }); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java index b7245b91f36c..1d349e6c96e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java @@ -44,4 +44,11 @@ public interface KeyguardTransitions { * Notify whether keyguard has created a remote animation runner for next app launch. */ default void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {} + + /** + * Notifies Shell to start a keyguard transition directly. + * @param keyguardShowing whether keyguard is showing or not. + * @param aodShowing whether aod is showing or not. + */ + default void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 2138acc51eb2..cbb08b804dfe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -1344,6 +1344,9 @@ public class PipTransition extends PipTransitionController { final SurfaceControl leash = pipChange.getLeash(); final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds(); final boolean isInPip = mPipTransitionState.isInPip(); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Update pip for unhandled transition, change=%s, destBounds=%s, isInPip=%b", + TAG, pipChange, destBounds, isInPip); mSurfaceTransactionHelper .crop(startTransaction, leash, destBounds) .round(startTransaction, leash, isInPip) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index af6844262771..7f6118689dad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -592,8 +592,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "onActivityRestartAttempt: %s", task.topActivity); - if (task.getWindowingMode() != WINDOWING_MODE_PINNED) { + "onActivityRestartAttempt: topActivity=%s, wasVisible=%b", + task.topActivity, wasVisible); + if (task.getWindowingMode() != WINDOWING_MODE_PINNED || !wasVisible) { return; } if (mPipTaskOrganizer.isLaunchToSplit(task)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java new file mode 100644 index 000000000000..f40a87c39aef --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2024 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.pip2.animation; + +import android.animation.Animator; +import android.animation.RectEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; +import android.view.Surface; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.shared.animation.Interpolators; + +/** + * Animator that handles bounds animations for entering PIP. + */ +public class PipEnterAnimator extends ValueAnimator + implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { + @NonNull private final SurfaceControl mLeash; + private final SurfaceControl.Transaction mStartTransaction; + private final SurfaceControl.Transaction mFinishTransaction; + + // Bounds updated by the evaluator as animator is running. + private final Rect mAnimatedRect = new Rect(); + + private final RectEvaluator mRectEvaluator; + private final Rect mEndBounds = new Rect(); + @Nullable private final Rect mSourceRectHint; + private final @Surface.Rotation int mRotation; + @Nullable private Runnable mAnimationStartCallback; + @Nullable private Runnable mAnimationEndCallback; + + private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; + + // Internal state representing initial transform - cached to avoid recalculation. + private final PointF mInitScale = new PointF(); + private final PointF mInitPos = new PointF(); + private final Rect mInitCrop = new Rect(); + + public PipEnterAnimator(Context context, + @NonNull SurfaceControl leash, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction, + @NonNull Rect endBounds, + @Nullable Rect sourceRectHint, + @Surface.Rotation int rotation) { + mLeash = leash; + mStartTransaction = startTransaction; + mFinishTransaction = finishTransaction; + mRectEvaluator = new RectEvaluator(mAnimatedRect); + mEndBounds.set(endBounds); + mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null; + mRotation = rotation; + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + + final int enterAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipEnterAnimationDuration); + setDuration(enterAnimationDuration); + setFloatValues(0f, 1f); + setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + addListener(this); + addUpdateListener(this); + } + + public void setAnimationStartCallback(@NonNull Runnable runnable) { + mAnimationStartCallback = runnable; + } + + public void setAnimationEndCallback(@NonNull Runnable runnable) { + mAnimationEndCallback = runnable; + } + + @Override + public void onAnimationStart(@NonNull Animator animation) { + if (mAnimationStartCallback != null) { + mAnimationStartCallback.run(); + } + if (mStartTransaction != null) { + onEnterAnimationUpdate(mInitScale, mInitPos, mInitCrop, + 0f /* fraction */, mStartTransaction); + mStartTransaction.apply(); + } + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + if (mAnimationEndCallback != null) { + mAnimationEndCallback.run(); + } + } + + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + final float fraction = getAnimatedFraction(); + onEnterAnimationUpdate(mInitScale, mInitPos, mInitCrop, fraction, tx); + tx.apply(); + } + + private void onEnterAnimationUpdate(PointF initScale, PointF initPos, Rect initCrop, + float fraction, SurfaceControl.Transaction tx) { + float scaleX = 1 + (initScale.x - 1) * (1 - fraction); + float scaleY = 1 + (initScale.y - 1) * (1 - fraction); + tx.setScale(mLeash, scaleX, scaleY); + + float posX = initPos.x + (mEndBounds.left - initPos.x) * fraction; + float posY = initPos.y + (mEndBounds.top - initPos.y) * fraction; + tx.setPosition(mLeash, posX, posY); + + Rect endCrop = new Rect(mEndBounds); + endCrop.offsetTo(0, 0); + mRectEvaluator.evaluate(fraction, initCrop, endCrop); + tx.setCrop(mLeash, mAnimatedRect); + } + + // no-ops + + @Override + public void onAnimationCancel(@NonNull Animator animation) {} + + @Override + public void onAnimationRepeat(@NonNull Animator animation) {} + + /** + * Caches the initial transform relevant values for the bounds enter animation. + * + * Since enter PiP makes use of a config-at-end transition, initial transform needs to be + * calculated differently from generic transitions. + * @param pipChange PiP change received as a transition target. + */ + public void setEnterStartState(@NonNull TransitionInfo.Change pipChange) { + PipUtils.calcStartTransform(pipChange, mInitScale, mInitPos, mInitCrop); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java index 8ebdc96c21a3..8fa5aa933929 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java @@ -19,7 +19,6 @@ package com.android.wm.shell.pip2.animation; import android.animation.Animator; import android.animation.RectEvaluator; import android.animation.ValueAnimator; -import android.annotation.IntDef; import android.content.Context; import android.graphics.Rect; import android.view.Surface; @@ -30,35 +29,22 @@ import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import com.android.wm.shell.shared.animation.Interpolators; /** - * Animator that handles bounds animations for entering / exiting PIP. + * Animator that handles bounds animations for exit-via-expanding PIP. */ -public class PipEnterExitAnimator extends ValueAnimator +public class PipExpandAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { - @IntDef(prefix = {"BOUNDS_"}, value = { - BOUNDS_ENTER, - BOUNDS_EXIT - }) - - @Retention(RetentionPolicy.SOURCE) - public @interface BOUNDS {} - - public static final int BOUNDS_ENTER = 0; - public static final int BOUNDS_EXIT = 1; - - @NonNull private final SurfaceControl mLeash; + @NonNull + private final SurfaceControl mLeash; private final SurfaceControl.Transaction mStartTransaction; private final SurfaceControl.Transaction mFinishTransaction; - private final int mEnterExitAnimationDuration; - private final @BOUNDS int mDirection; private final @Surface.Rotation int mRotation; // optional callbacks for tracking animation start and end - @Nullable private Runnable mAnimationStartCallback; + @Nullable + private Runnable mAnimationStartCallback; @Nullable private Runnable mAnimationEndCallback; private final Rect mBaseBounds = new Rect(); @@ -78,7 +64,7 @@ public class PipEnterExitAnimator extends ValueAnimator private final RectEvaluator mInsetEvaluator; private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper; - public PipEnterExitAnimator(Context context, + public PipExpandAnimator(Context context, @NonNull SurfaceControl leash, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, @@ -86,7 +72,6 @@ public class PipEnterExitAnimator extends ValueAnimator @NonNull Rect startBounds, @NonNull Rect endBounds, @Nullable Rect sourceRectHint, - @BOUNDS int direction, @Surface.Rotation int rotation) { mLeash = leash; mStartTransaction = startTransaction; @@ -98,7 +83,6 @@ public class PipEnterExitAnimator extends ValueAnimator mRectEvaluator = new RectEvaluator(mAnimatedRect); mInsetEvaluator = new RectEvaluator(new Rect()); mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context); - mDirection = direction; mRotation = rotation; mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null; @@ -113,12 +97,14 @@ public class PipEnterExitAnimator extends ValueAnimator mSurfaceControlTransactionFactory = new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); - mEnterExitAnimationDuration = context.getResources() + + final int enterAnimationDuration = context.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); + setDuration(enterAnimationDuration); setObjectValues(startBounds, endBounds); - setDuration(mEnterExitAnimationDuration); setEvaluator(mRectEvaluator); + setInterpolator(Interpolators.FAST_OUT_SLOW_IN); addListener(this); addUpdateListener(this); } @@ -147,9 +133,10 @@ public class PipEnterExitAnimator extends ValueAnimator // finishTransaction might override some state (eg. corner radii) so we want to // manually set the state to the end of the animation mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, mSourceRectHint, - mBaseBounds, mAnimatedRect, getInsets(1f), isInPipDirection(), 1f) - .round(mFinishTransaction, mLeash, isInPipDirection()) - .shadow(mFinishTransaction, mLeash, isInPipDirection()); + mBaseBounds, mAnimatedRect, getInsets(1f), + false /* isInPipDirection */, 1f) + .round(mFinishTransaction, mLeash, false /* applyCornerRadius */) + .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */); } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); @@ -160,32 +147,22 @@ public class PipEnterExitAnimator extends ValueAnimator public void onAnimationUpdate(@NonNull ValueAnimator animation) { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); final float fraction = getAnimatedFraction(); - Rect insets = getInsets(fraction); // TODO (b/350801661): implement fixed rotation + Rect insets = getInsets(fraction); mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, - mBaseBounds, mAnimatedRect, insets, isInPipDirection(), fraction) - .round(tx, mLeash, isInPipDirection()) - .shadow(tx, mLeash, isInPipDirection()); + mBaseBounds, mAnimatedRect, insets, false /* isInPipDirection */, fraction) + .round(tx, mLeash, false /* applyCornerRadius */) + .shadow(tx, mLeash, false /* applyCornerRadius */); tx.apply(); } - private Rect getInsets(float fraction) { - Rect startInsets = isInPipDirection() ? mZeroInsets : mSourceRectHintInsets; - Rect endInsets = isInPipDirection() ? mSourceRectHintInsets : mZeroInsets; - + final Rect startInsets = mSourceRectHintInsets; + final Rect endInsets = mZeroInsets; return mInsetEvaluator.evaluate(fraction, startInsets, endInsets); } - private boolean isInPipDirection() { - return mDirection == BOUNDS_ENTER; - } - - private boolean isOutPipDirection() { - return mDirection == BOUNDS_EXIT; - } - // no-ops @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 9cfe1620a2ff..8c1e5e6a3e84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -18,10 +18,12 @@ package com.android.wm.shell.pip2.phone; import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.RemoteAction; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.RemoteException; @@ -52,7 +54,8 @@ import java.util.List; * The current media session provides actions whenever there are no valid actions provided by the * current PiP activity. Otherwise, those actions always take precedence. */ -public class PhonePipMenuController implements PipMenuController { +public class PhonePipMenuController implements PipMenuController, + PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = "PhonePipMenuController"; private static final boolean DEBUG = false; @@ -113,6 +116,11 @@ public class PhonePipMenuController implements PipMenuController { private PipMenuView mPipMenuView; + private final PipTaskListener mPipTaskListener; + + @NonNull + private final PipTransitionState mPipTransitionState; + private SurfaceControl mLeash; private ActionListener mMediaActionListener = new ActionListener() { @@ -125,15 +133,27 @@ public class PhonePipMenuController implements PipMenuController { public PhonePipMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController mediaController, SystemWindows systemWindows, - PipUiEventLogger pipUiEventLogger, - ShellExecutor mainExecutor, Handler mainHandler) { + PipUiEventLogger pipUiEventLogger, PipTaskListener pipTaskListener, + @NonNull PipTransitionState pipTransitionState, ShellExecutor mainExecutor, + Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; mMediaController = mediaController; mSystemWindows = systemWindows; + mPipTaskListener = pipTaskListener; + mPipTransitionState = pipTransitionState; mMainExecutor = mainExecutor; mMainHandler = mainHandler; mPipUiEventLogger = pipUiEventLogger; + + mPipTransitionState.addPipTransitionStateChangedListener(this); + + mPipTaskListener.addParamsChangedListener(new PipTaskListener.PipParamsChangedCallback() { + @Override + public void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { + setAppActions(actions, closeAction); + } + }); } public boolean isMenuVisible() { @@ -438,8 +458,7 @@ public class PhonePipMenuController implements PipMenuController { * Sets the menu actions to the actions provided by the current PiP menu. */ @Override - public void setAppActions(List<RemoteAction> appActions, - RemoteAction closeAction) { + public void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction) { mAppActions = appActions; mCloseAction = closeAction; updateMenuActions(); @@ -468,8 +487,8 @@ public class PhonePipMenuController implements PipMenuController { */ private void updateMenuActions() { if (mPipMenuView != null) { - mPipMenuView.setActions(mPipBoundsState.getBounds(), - resolveMenuActions(), mCloseAction); + mPipMenuView.setActions(mPipBoundsState.getBounds(), resolveMenuActions(), + mCloseAction); } } @@ -567,6 +586,29 @@ public class PhonePipMenuController implements PipMenuController { } } + @Override + public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, Bundle extra) { + switch (newState) { + case PipTransitionState.ENTERED_PIP: + attach(mPipTransitionState.mPinnedTaskLeash); + break; + case PipTransitionState.EXITED_PIP: + detach(); + break; + case PipTransitionState.CHANGED_PIP_BOUNDS: + updateMenuLayout(mPipBoundsState.getBounds()); + hideMenu(); + break; + case PipTransitionState.CHANGING_PIP_BOUNDS: + hideMenu(); + break; + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + hideMenu(); + break; + } + } + void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index e9c4c14234e6..73be8db0ea8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -324,10 +324,16 @@ public class PipController implements ConfigurationChangeListener, int launcherRotation, Rect hotseatKeepClearArea) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "getSwipePipToHomeBounds: %s", componentName); - // preemptively add the keep clear area for Hotseat, so that it is taken into account - // when calculating the entry destination bounds of PiP window + // Preemptively add the keep clear area for Hotseat, so that it is taken into account + // when calculating the entry destination bounds of PiP window. mPipBoundsState.setNamedUnrestrictedKeepClearArea( PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea); + + // Set the display layout rotation early to calculate final orientation bounds that + // the animator expects, this will also be used to detect the fixed rotation when + // Shell resolves the type of the animation we are undergoing. + mPipDisplayLayoutState.rotateTo(launcherRotation); + mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams, mPipBoundsAlgorithm); return mPipBoundsAlgorithm.getEntryDestinationBounds(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java index a29104c4aafd..0910919b3064 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java @@ -447,7 +447,7 @@ public class PipMenuView extends FrameLayout { final LayoutInflater inflater = LayoutInflater.from(mContext); while (mActionsGroup.getChildCount() < mActions.size()) { final PipMenuActionView actionView = (PipMenuActionView) inflater.inflate( - R.layout.pip_menu_action, mActionsGroup, false); + R.layout.pip2_menu_action, mActionsGroup, false); mActionsGroup.addView(actionView); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 0324fdba0fbf..268c3a20a41a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -336,7 +336,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } cancelPhysicsAnimation(); mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */); - // mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit); + mPipScheduler.scheduleExitPipViaExpand(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index f4defdc7963c..d4f190ebd2a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -52,7 +52,6 @@ public class PipScheduler { private final Context mContext; private final PipBoundsState mPipBoundsState; - private final PhonePipMenuController mPipMenuController; private final ShellExecutor mMainExecutor; private final PipTransitionState mPipTransitionState; private PipSchedulerReceiver mSchedulerReceiver; @@ -97,12 +96,10 @@ public class PipScheduler { public PipScheduler(Context context, PipBoundsState pipBoundsState, - PhonePipMenuController pipMenuController, ShellExecutor mainExecutor, PipTransitionState pipTransitionState) { mContext = context; mPipBoundsState = pipBoundsState; - mPipMenuController = pipMenuController; mMainExecutor = mainExecutor; mPipTransitionState = pipTransitionState; @@ -263,7 +260,6 @@ public class PipScheduler { return; } mPipBoundsState.setBounds(newBounds); - mPipMenuController.updateMenuLayout(newBounds); maybeUpdateMovementBounds(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 262c14d2bfe3..c58de2c3512a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -20,6 +20,7 @@ import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHA import android.app.ActivityManager; import android.app.PictureInPictureParams; +import android.app.RemoteAction; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; @@ -37,6 +38,9 @@ import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip2.animation.PipResizeAnimator; import com.android.wm.shell.shared.annotations.ShellMainThread; +import java.util.ArrayList; +import java.util.List; + /** * A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via * Transitions framework directly. @@ -57,6 +61,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, new PictureInPictureParams.Builder().build(); private boolean mWaitingForAspectRatioChange = false; + private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>(); public PipTaskListener(Context context, ShellTaskOrganizer shellTaskOrganizer, @@ -85,10 +90,25 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.equals(params)) { return; } + if (PipUtils.remoteActionsChanged(params.getActions(), mPictureInPictureParams.getActions()) + || !PipUtils.remoteActionsMatch(params.getCloseAction(), + mPictureInPictureParams.getCloseAction())) { + for (PipParamsChangedCallback listener : mPipParamsChangedListeners) { + listener.onActionsChanged(params.getActions(), params.getCloseAction()); + } + } mPictureInPictureParams.copyOnlySet(params != null ? params : new PictureInPictureParams.Builder().build()); } + /** Add a PipParamsChangedCallback listener. */ + public void addParamsChangedListener(PipParamsChangedCallback listener) { + if (mPipParamsChangedListeners.contains(listener)) { + return; + } + mPipParamsChangedListeners.add(listener); + } + @NonNull public PictureInPictureParams getPictureInPictureParams() { return mPictureInPictureParams; @@ -164,4 +184,12 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, break; } } + + public interface PipParamsChangedCallback { + /** + * Called if either the actions or the close action changed. + */ + default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index f93233ec7461..b57f51aff176 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -17,6 +17,7 @@ package com.android.wm.shell.pip2.phone; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.Surface.ROTATION_270; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; @@ -33,6 +34,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.Context; +import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -49,11 +51,14 @@ import com.android.internal.util.Preconditions; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMenuController; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; -import com.android.wm.shell.pip2.animation.PipEnterExitAnimator; +import com.android.wm.shell.pip2.animation.PipEnterAnimator; +import com.android.wm.shell.pip2.animation.PipExpandAnimator; +import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.shared.pip.PipContentOverlay; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -81,7 +86,7 @@ public class PipTransition extends PipTransitionController implements * The fixed start delay in ms when fading out the content overlay from bounds animation. * The fadeout animation is guaranteed to start after the client has drawn under the new config. */ - private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400; + private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500; // // Dependencies @@ -91,6 +96,7 @@ public class PipTransition extends PipTransitionController implements private final PipTaskListener mPipTaskListener; private final PipScheduler mPipScheduler; private final PipTransitionState mPipTransitionState; + private final PipDisplayLayoutState mPipDisplayLayoutState; // // Transition caches @@ -123,6 +129,7 @@ public class PipTransition extends PipTransitionController implements PipTaskListener pipTaskListener, PipScheduler pipScheduler, PipTransitionState pipTransitionState, + PipDisplayLayoutState pipDisplayLayoutState, PipUiStateChangeController pipUiStateChangeController) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, pipBoundsAlgorithm); @@ -133,6 +140,7 @@ public class PipTransition extends PipTransitionController implements mPipScheduler.setPipTransitionController(this); mPipTransitionState = pipTransitionState; mPipTransitionState.addPipTransitionStateChangedListener(this); + mPipDisplayLayoutState = pipDisplayLayoutState; } @Override @@ -211,6 +219,7 @@ public class PipTransition extends PipTransitionController implements @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + mFinishCallback = finishCallback; if (transition == mEnterTransition || info.getType() == TRANSIT_PIP) { mEnterTransition = null; // If we are in swipe PiP to Home transition we are ENTERING_PIP as a jumpcut transition @@ -251,6 +260,7 @@ public class PipTransition extends PipTransitionController implements if (isRemovePipTransition(info)) { return removePipImmediately(info, startTransaction, finishTransaction, finishCallback); } + mFinishCallback = null; return false; } @@ -290,7 +300,6 @@ public class PipTransition extends PipTransitionController implements mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION; } - mFinishCallback = finishCallback; mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra); return true; } @@ -303,54 +312,46 @@ public class PipTransition extends PipTransitionController implements if (pipChange == null) { return false; } - WindowContainerToken pipTaskToken = pipChange.getContainer(); SurfaceControl pipLeash = pipChange.getLeash(); + Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition."); - if (pipTaskToken == null || pipLeash == null) { - return false; - } - - SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay(); - PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; - - Rect appBounds = mPipTransitionState.getSwipePipToHomeAppBounds(); - Rect destinationBounds = pipChange.getEndAbsBounds(); - - float aspectRatio = pipChange.getTaskInfo().pictureInPictureParams.getAspectRatioFloat(); - - // We fake the source rect hint when the one prvided by the app is invalid for - // the animation with an app icon overlay. - Rect animationSrcRectHint = overlayLeash == null ? params.getSourceRectHint() - : PipUtils.getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio); - - WindowContainerTransaction finishWct = new WindowContainerTransaction(); - SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); - - final float scale = (float) destinationBounds.width() / animationSrcRectHint.width(); - startTransaction.setWindowCrop(pipLeash, animationSrcRectHint); - startTransaction.setPosition(pipLeash, - destinationBounds.left - animationSrcRectHint.left * scale, - destinationBounds.top - animationSrcRectHint.top * scale); - startTransaction.setScale(pipLeash, scale, scale); - - if (overlayLeash != null) { + final Rect destinationBounds = pipChange.getEndAbsBounds(); + final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay(); + if (swipePipToHomeOverlay != null) { final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize( mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds); - - // Overlay needs to be adjusted once a new draw comes in resetting surface transform. - tx.setScale(overlayLeash, 1f, 1f); - tx.setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f, - (destinationBounds.height() - overlaySize) / 2f); + // It is possible we reparent the PIP activity to a new PIP task (in multi-activity + // apps), so we should also reparent the overlay to the final PIP task. + startTransaction.reparent(swipePipToHomeOverlay, pipLeash) + .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE) + .setScale(swipePipToHomeOverlay, 1f, 1f) + .setPosition(swipePipToHomeOverlay, + (destinationBounds.width() - overlaySize) / 2f, + (destinationBounds.height() - overlaySize) / 2f); + } + startTransaction.merge(finishTransaction); + + final int startRotation = pipChange.getStartRotation(); + final int endRotation = mPipDisplayLayoutState.getRotation(); + if (endRotation != startRotation) { + boolean isClockwise = (endRotation - startRotation) == -ROTATION_270; + + // Display bounds were already updated to represent the final orientation, + // so we just need to readjust the origin, and perform rotation about (0, 0). + Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); + int originTranslateX = isClockwise ? 0 : -displayBounds.width(); + int originTranslateY = isClockwise ? -displayBounds.height() : 0; + + Matrix transformTensor = new Matrix(); + final float[] matrixTmp = new float[9]; + transformTensor.setTranslate(originTranslateX + destinationBounds.left, + originTranslateY + destinationBounds.top); + final float degrees = (endRotation - startRotation) * 90f; + transformTensor.postRotate(degrees); + startTransaction.setMatrix(pipLeash, transformTensor, matrixTmp); } startTransaction.apply(); - - tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), - this::onClientDrawAtTransitionEnd); - finishWct.setBoundsChangeTransaction(pipTaskToken, tx); - - // Note that finishWct should be free of any actual WM state changes; we are using - // it for syncing with the client draw after delayed configuration changes are dispatched. - finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct); + finishInner(); return true; } @@ -386,14 +387,6 @@ public class PipTransition extends PipTransitionController implements return false; } - WindowContainerToken pipTaskToken = pipChange.getContainer(); - if (pipTaskToken == null) { - return false; - } - - WindowContainerTransaction finishWct = new WindowContainerTransaction(); - SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); - Rect startBounds = pipChange.getStartAbsBounds(); Rect endBounds = pipChange.getEndAbsBounds(); SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; @@ -405,17 +398,22 @@ public class PipTransition extends PipTransitionController implements sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); } - PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash, - startTransaction, finishTransaction, startBounds, startBounds, endBounds, - sourceRectHint, PipEnterExitAnimator.BOUNDS_ENTER, Surface.ROTATION_0); - - tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), - this::onClientDrawAtTransitionEnd); - finishWct.setBoundsChangeTransaction(pipTaskToken, tx); - - animator.setAnimationEndCallback(() -> - finishCallback.onTransitionFinished(finishWct)); + // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, + // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f + // by the Transitions framework to simplify Task opening transitions. + if (TransitionUtil.isOpeningType(info.getType())) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getLeash() == null) continue; + if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { + startTransaction.setAlpha(change.getLeash(), 1f); + } + } + } + PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, + startTransaction, finishTransaction, endBounds, sourceRectHint, Surface.ROTATION_0); + animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange)); + animator.setAnimationEndCallback(this::finishInner); animator.start(); return true; } @@ -440,11 +438,8 @@ public class PipTransition extends PipTransitionController implements PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction, PipAlphaAnimator.FADE_IN); - animator.setAnimationEndCallback(() -> { - finishCallback.onTransitionFinished(null); - // This should update the pip transition state accordingly after we stop playing. - onClientDrawAtTransitionEnd(); - }); + // This should update the pip transition state accordingly after we stop playing. + animator.setAnimationEndCallback(this::finishInner); animator.start(); return true; @@ -498,9 +493,9 @@ public class PipTransition extends PipTransitionController implements sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint(); } - PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash, + PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash, startTransaction, finishTransaction, endBounds, startBounds, endBounds, - sourceRectHint, PipEnterExitAnimator.BOUNDS_EXIT, Surface.ROTATION_0); + sourceRectHint, Surface.ROTATION_0); animator.setAnimationEndCallback(() -> { mPipTransitionState.setState(PipTransitionState.EXITED_PIP); @@ -618,7 +613,8 @@ public class PipTransition extends PipTransitionController implements // Miscellaneous callbacks and listeners // - private void onClientDrawAtTransitionEnd() { + private void finishInner() { + finishTransition(null /* tx */); if (mPipTransitionState.getSwipePipToHomeOverlay() != null) { startOverlayFadeoutAnimation(); } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) { @@ -640,6 +636,7 @@ public class PipTransition extends PipTransitionController implements } if (mFinishCallback != null) { mFinishCallback.onTransitionFinished(wct); + mFinishCallback = null; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 03ff1aac794c..6086801491e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -32,13 +32,14 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Color; +import android.graphics.Point; import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.window.DesktopModeFlags; import android.window.WindowContainerToken; -import android.window.flags.DesktopModeFlags; import androidx.annotation.BinderThread; import androidx.annotation.NonNull; @@ -46,13 +47,14 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.ProtoLog; +import com.android.window.flags.Flags; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.GroupedRecentTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; @@ -79,14 +81,14 @@ import java.util.function.Consumer; * Manages the recent task list from the system, caching it as necessary. */ public class RecentTasksController implements TaskStackListenerCallback, - RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener, + RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener, TaskStackTransitionObserver.TaskStackTransitionObserverListener { private static final String TAG = RecentTasksController.class.getSimpleName(); private final Context mContext; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; - private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository; + private final Optional<DesktopRepository> mDesktopRepository; private final ShellExecutor mMainExecutor; private final TaskStackListenerImpl mTaskStackListener; private final RecentTasksImpl mImpl = new RecentTasksImpl(); @@ -119,7 +121,7 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopModeTaskRepository> desktopModeTaskRepository, + Optional<DesktopRepository> desktopRepository, TaskStackTransitionObserver taskStackTransitionObserver, @ShellMainThread ShellExecutor mainExecutor ) { @@ -127,7 +129,7 @@ public class RecentTasksController implements TaskStackListenerCallback, return null; } return new RecentTasksController(context, shellInit, shellController, shellCommandHandler, - taskStackListener, activityTaskManager, desktopModeTaskRepository, + taskStackListener, activityTaskManager, desktopRepository, taskStackTransitionObserver, mainExecutor); } @@ -137,7 +139,7 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopModeTaskRepository> desktopModeTaskRepository, + Optional<DesktopRepository> desktopRepository, TaskStackTransitionObserver taskStackTransitionObserver, ShellExecutor mainExecutor) { mContext = context; @@ -146,7 +148,7 @@ public class RecentTasksController implements TaskStackListenerCallback, mActivityTaskManager = activityTaskManager; mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mTaskStackListener = taskStackListener; - mDesktopModeTaskRepository = desktopModeTaskRepository; + mDesktopRepository = desktopRepository; mTaskStackTransitionObserver = taskStackTransitionObserver; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); @@ -166,7 +168,7 @@ public class RecentTasksController implements TaskStackListenerCallback, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); mTaskStackListener.addListener(this); - mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this)); + mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this)); if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this, mMainExecutor); @@ -415,14 +417,24 @@ public class RecentTasksController implements TaskStackListenerCallback, } if (DesktopModeStatus.canEnterDesktopMode(mContext) - && mDesktopModeTaskRepository.isPresent() - && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) { + && mDesktopRepository.isPresent() + && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) { // Freeform tasks will be added as a separate entry if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) { mostRecentFreeformTaskIndex = recentTasks.size(); } + // If task has their app bounds set to null which happens after reboot, set the + // app bounds to persisted lastFullscreenBounds. Also set the position in parent + // to the top left of the bounds. + if (Flags.enableDesktopWindowingPersistence() + && taskInfo.configuration.windowConfiguration.getAppBounds() == null) { + taskInfo.configuration.windowConfiguration.setAppBounds( + taskInfo.lastNonFullscreenBounds); + taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left, + taskInfo.lastNonFullscreenBounds.top); + } freeformTasks.add(taskInfo); - if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) { + if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) { minimizedFreeformTasks.add(taskInfo.taskId); } continue; @@ -528,6 +540,14 @@ public class RecentTasksController implements TaskStackListenerCallback, return null; } + /** + * Remove the background task that match the given taskId. This will remove the task regardless + * of whether it's active or recent. + */ + public boolean removeBackgroundTask(int taskId) { + return mActivityTaskManager.removeTask(taskId); + } + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 8077aeebf27f..f7ed1dd4606b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -52,7 +52,6 @@ import android.util.ArrayMap; import android.util.IntArray; import android.util.Pair; import android.util.Slog; -import android.view.Display; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.PictureInPictureSurfaceTransaction; @@ -910,6 +909,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, "task #" + taskInfo.taskId + " is always_on_top"); return; } + if (TransitionUtil.isClosingType(change.getMode()) + && taskInfo != null && taskInfo.lastParentTaskIdBeforePip > 0) { + // Pinned task is closing as a side effect of the removal of its original Task, + // such transition should be handled by PiP. So cancel the merge here. + cancel(false /* toHome */, false /* withScreenshots */, + "task #" + taskInfo.taskId + " is removed with its original parent"); + return; + } final boolean isRootTask = taskInfo != null && TransitionInfo.isIndependent(change, info); final boolean isRecentsTask = mRecentsTask != null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt index e5bfccf0682e..1af99f974a28 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt @@ -17,14 +17,12 @@ package com.android.wm.shell.recents import android.app.ActivityManager.RunningTaskInfo -import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.os.IBinder import android.util.ArrayMap import android.view.SurfaceControl -import android.view.WindowManager import android.window.TransitionInfo +import android.window.DesktopModeFlags import com.android.wm.shell.shared.TransitionUtil -import android.window.flags.DesktopModeFlags import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import dagger.Lazy @@ -40,16 +38,11 @@ class TaskStackTransitionObserver( private val transitions: Lazy<Transitions>, shellInit: ShellInit ) : Transitions.TransitionObserver { - - private val transitionToTransitionChanges: MutableMap<IBinder, TransitionChanges> = - mutableMapOf() private val taskStackTransitionObserverListeners = ArrayMap<TaskStackTransitionObserverListener, Executor>() init { - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - shellInit.addInitCallback(::onInit, this) - } + shellInit.addInitCallback(::onInit, this) } fun onInit() { @@ -63,9 +56,6 @@ class TaskStackTransitionObserver( finishTransaction: SurfaceControl.Transaction ) { if (DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue) { - val taskInfoList = mutableListOf<RunningTaskInfo>() - val transitionTypeList = mutableListOf<Int>() - for (change in info.changes) { if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) { continue @@ -76,59 +66,21 @@ class TaskStackTransitionObserver( continue } - // Filter out changes that we care about - if (change.mode == WindowManager.TRANSIT_OPEN) { - change.taskInfo?.let { taskInfoList.add(it) } - transitionTypeList.add(change.mode) + // Find the first task that is opening, this should be the one at the front after + // the transition + if (TransitionUtil.isOpeningType(change.mode)) { + notifyTaskStackTransitionObserverListeners(taskInfo) + break } } - // Only add the transition to map if it has a change we care about - if (taskInfoList.isNotEmpty()) { - transitionToTransitionChanges.put( - transition, - TransitionChanges(taskInfoList, transitionTypeList) - ) - } } } override fun onTransitionStarting(transition: IBinder) {} - override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - val mergedTransitionChanges = - transitionToTransitionChanges.get(merged) - ?: - // We are adding changes of the merged transition to changes of the playing - // transition so if there is no changes nothing to do. - return + override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} - transitionToTransitionChanges.remove(merged) - val playingTransitionChanges = transitionToTransitionChanges.get(playing) - if (playingTransitionChanges != null) { - playingTransitionChanges.merge(mergedTransitionChanges) - } else { - transitionToTransitionChanges.put(playing, mergedTransitionChanges) - } - } - - override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { - val taskInfoList = - transitionToTransitionChanges.getOrDefault(transition, TransitionChanges()).taskInfoList - val typeList = - transitionToTransitionChanges - .getOrDefault(transition, TransitionChanges()) - .transitionTypeList - transitionToTransitionChanges.remove(transition) - - for ((index, taskInfo) in taskInfoList.withIndex()) { - if ( - TransitionUtil.isOpeningType(typeList[index]) && - taskInfo.windowingMode == WINDOWING_MODE_FREEFORM - ) { - notifyTaskStackTransitionObserverListeners(taskInfo) - } - } - } + override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {} fun addTaskStackTransitionObserverListener( taskStackTransitionObserverListener: TaskStackTransitionObserverListener, @@ -154,14 +106,4 @@ class TaskStackTransitionObserver( /** Called when a task is moved to front. */ fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {} } - - private data class TransitionChanges( - val taskInfoList: MutableList<RunningTaskInfo> = ArrayList(), - val transitionTypeList: MutableList<Int> = ArrayList(), - ) { - fun merge(transitionChanges: TransitionChanges) { - taskInfoList.addAll(transitionChanges.taskInfoList) - transitionTypeList.addAll(transitionChanges.transitionTypeList) - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index e8eb10c984af..e527c02e0dec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -901,6 +901,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setEnterInstanceId(instanceId); } + + @Override + public void setExcludeImeInsets(boolean exclude) { + if (android.view.inputmethod.Flags.refactorInsetsController()) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (mRootTaskInfo == null) { + ProtoLog.e(WM_SHELL_SPLIT_SCREEN, "setExcludeImeInsets: mRootTaskInfo is null"); + return; + } + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "setExcludeImeInsets: root taskId=%s exclude=%s", + mRootTaskInfo.taskId, exclude); + wct.setExcludeImeInsets(mRootTaskInfo.token, exclude); + mTaskOrganizer.applyTransaction(wct); + } + } + /** * Checks if either of the apps in the desired split launch is currently in Pip. If so, it will * launch the non-pipped app as a fullscreen app, otherwise no-op. @@ -1717,6 +1734,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true); wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); + // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); setRootForceTranslucent(true, wct); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 6e084d6e05a4..27472493a8bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -146,6 +146,11 @@ public class TaskSnapshotWindow { Slog.w(TAG, "Failed to relayout snapshot starting window"); return null; } + if (!surfaceControl.isValid()) { + snapshotSurface.clearWindowSynced(); + Slog.w(TAG, "Unable to draw snapshot, no valid surface"); + return null; + } SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot, info.taskBounds, topWindowInsetsState, true /* releaseAfterDraw */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index d3bed59f7994..a2439a937512 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -361,8 +361,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final int anim = getRotationAnimationHint(change, info, mDisplayController); isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS; if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) { - startRotationAnimation(startTransaction, change, info, anim, animations, - onAnimFinish); + final int flags = wallpaperTransit != WALLPAPER_TRANSITION_NONE + && Flags.commonSurfaceAnimator() + ? ScreenRotationAnimation.FLAG_HAS_WALLPAPER : 0; + startRotationAnimation(startTransaction, change, info, anim, flags, + animations, onAnimFinish); isDisplayRotationAnimationStarted = true; continue; } @@ -414,7 +417,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY) && change.getStartRotation() != change.getEndRotation()) { startRotationAnimation(startTransaction, change, info, - ROTATION_ANIMATION_ROTATE, animations, onAnimFinish); + ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish); continue; } } @@ -699,12 +702,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } private void startRotationAnimation(SurfaceControl.Transaction startTransaction, - TransitionInfo.Change change, TransitionInfo info, int animHint, + TransitionInfo.Change change, TransitionInfo info, int animHint, int flags, ArrayList<Animator> animations, Runnable onAnimFinish) { final int rootIdx = TransitionUtil.rootIndexFor(change, info); final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(), - animHint); + animHint, flags); // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real // content, and background color. The item of "animGroup" will be removed if the sub // animation is finished. Then if the list becomes empty, the rotation animation is done. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java index 2f5059f3161c..6d01e247b48d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java @@ -16,7 +16,9 @@ package com.android.wm.shell.transition; -import static android.view.Display.INVALID_DISPLAY; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; @@ -24,10 +26,10 @@ import static com.android.wm.shell.transition.Transitions.TransitionObserver; import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; -import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import android.util.Slog; -import android.view.SurfaceControl; +import android.util.SparseArray; import android.window.TransitionInfo; import com.android.wm.shell.shared.FocusTransitionListener; @@ -43,45 +45,64 @@ import java.util.concurrent.Executor; * It reports transitions to callers outside of the process via {@link IFocusTransitionListener}, * and callers within the process via {@link FocusTransitionListener}. */ -public class FocusTransitionObserver implements TransitionObserver { +public class FocusTransitionObserver { private static final String TAG = FocusTransitionObserver.class.getSimpleName(); private IFocusTransitionListener mRemoteListener; private final Map<FocusTransitionListener, Executor> mLocalListeners = new HashMap<>(); - private int mFocusedDisplayId = INVALID_DISPLAY; + private int mFocusedDisplayId = DEFAULT_DISPLAY; + private final SparseArray<RunningTaskInfo> mFocusedTaskOnDisplay = new SparseArray<>(); + + private final ArraySet<RunningTaskInfo> mTmpTasksToBeNotified = new ArraySet<>(); public FocusTransitionObserver() {} - @Override - public void onTransitionReady(@NonNull IBinder transition, - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction) { + /** + * Update display/window focus state from the given transition info and notifies changes if any. + */ + public void updateFocusState(@NonNull TransitionInfo info) { + if (!enableDisplayFocusInShellTransitions()) { + return; + } final List<TransitionInfo.Change> changes = info.getChanges(); for (int i = changes.size() - 1; i >= 0; i--) { final TransitionInfo.Change change = changes.get(i); + final RunningTaskInfo task = change.getTaskInfo(); - if (task != null && task.isFocused && change.hasFlags(FLAG_MOVED_TO_TOP)) { - if (mFocusedDisplayId != task.displayId) { - mFocusedDisplayId = task.displayId; + if (task != null + && (change.hasFlags(FLAG_MOVED_TO_TOP) || change.getMode() == TRANSIT_OPEN)) { + final RunningTaskInfo lastFocusedTaskOnDisplay = + mFocusedTaskOnDisplay.get(task.displayId); + if (lastFocusedTaskOnDisplay != null) { + mTmpTasksToBeNotified.add(lastFocusedTaskOnDisplay); + } + mTmpTasksToBeNotified.add(task); + mFocusedTaskOnDisplay.put(task.displayId, task); + } + + if (change.hasFlags(FLAG_IS_DISPLAY) && change.hasFlags(FLAG_MOVED_TO_TOP)) { + if (mFocusedDisplayId != change.getEndDisplayId()) { + final RunningTaskInfo lastGloballyFocusedTask = + mFocusedTaskOnDisplay.get(mFocusedDisplayId); + if (lastGloballyFocusedTask != null) { + mTmpTasksToBeNotified.add(lastGloballyFocusedTask); + } + mFocusedDisplayId = change.getEndDisplayId(); notifyFocusedDisplayChanged(); + final RunningTaskInfo currentGloballyFocusedTask = + mFocusedTaskOnDisplay.get(mFocusedDisplayId); + if (currentGloballyFocusedTask != null) { + mTmpTasksToBeNotified.add(currentGloballyFocusedTask); + } } - return; } } + mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged); + mTmpTasksToBeNotified.clear(); } - @Override - public void onTransitionStarting(@NonNull IBinder transition) {} - - @Override - public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {} - - @Override - public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {} - /** * Sets the focus transition listener that receives any transitions resulting in focus switch. * This is for calls from outside the Shell, within the host process. @@ -93,7 +114,10 @@ public class FocusTransitionObserver implements TransitionObserver { return; } mLocalListeners.put(listener, executor); - executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId)); + executor.execute(() -> { + listener.onFocusedDisplayChanged(mFocusedDisplayId); + mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged); + }); } /** @@ -121,13 +145,20 @@ public class FocusTransitionObserver implements TransitionObserver { notifyFocusedDisplayChangedToRemote(); } - /** - * Notifies the listener that display focus has changed. - */ - public void notifyFocusedDisplayChanged() { + private void notifyTaskFocusChanged(RunningTaskInfo task) { + final boolean isFocusedOnDisplay = isFocusedOnDisplay(task); + final boolean isFocusedGlobally = hasGlobalFocus(task); + mLocalListeners.forEach((listener, executor) -> + executor.execute(() -> listener.onFocusedTaskChanged(task.taskId, + isFocusedOnDisplay, isFocusedGlobally))); + } + + private void notifyFocusedDisplayChanged() { notifyFocusedDisplayChangedToRemote(); mLocalListeners.forEach((listener, executor) -> - executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId))); + executor.execute(() -> { + listener.onFocusedDisplayChanged(mFocusedDisplayId); + })); } private void notifyFocusedDisplayChangedToRemote() { @@ -139,4 +170,23 @@ public class FocusTransitionObserver implements TransitionObserver { } } } + + private boolean isFocusedOnDisplay(@NonNull RunningTaskInfo task) { + if (!enableDisplayFocusInShellTransitions()) { + return task.isFocused; + } + final RunningTaskInfo focusedTaskOnDisplay = mFocusedTaskOnDisplay.get(task.displayId); + return focusedTaskOnDisplay != null && focusedTaskOnDisplay.taskId == task.taskId; + } + + /** + * Checks whether the given task has focused globally on the system. + * (Note {@link RunningTaskInfo#isFocused} represents per-display focus.) + */ + public boolean hasGlobalFocus(@NonNull RunningTaskInfo task) { + if (!enableDisplayFocusInShellTransitions()) { + return task.isFocused; + } + return task.displayId == mFocusedDisplayId && isFocusedOnDisplay(task); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index 30d7245436be..e61929fef312 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -141,10 +141,13 @@ public class MixedTransitionHelper { pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, finishCB); + // make a new finishTransaction because pip's startEnterAnimation "consumes" it so + // we need a separate one to send over to launcher. + SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction(); // Dispatch the rest of the transition normally. This will most-likely be taken by // recents or default handler. mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse, - otherStartT, finishTransaction, finishCB, mixedHandler); + otherStartT, otherFinishT, finishCB, mixedHandler); } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " + "forward animation to Pip-Handler."); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index 5802e2ca8133..1a04997fa384 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -25,12 +25,9 @@ import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurf import static com.android.wm.shell.transition.Transitions.TAG; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Rect; import android.hardware.HardwareBuffer; @@ -38,6 +35,7 @@ import android.util.Slog; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.window.ScreenCapture; @@ -74,6 +72,7 @@ import java.util.ArrayList; */ class ScreenRotationAnimation { static final int MAX_ANIMATION_DURATION = 10 * 1000; + static final int FLAG_HAS_WALLPAPER = 1; private final Context mContext; private final TransactionPool mTransactionPool; @@ -98,6 +97,12 @@ class ScreenRotationAnimation { private SurfaceControl mBackColorSurface; /** The leash using to animate screenshot layer. */ private final SurfaceControl mAnimLeash; + /** + * The container with background color for {@link #mSurfaceControl}. It is only created if + * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed). + * That prevents flickering of alpha blending. + */ + private SurfaceControl mBackEffectSurface; // The current active animation to move from the old to the new rotated // state. Which animation is run here will depend on the old and new @@ -111,8 +116,8 @@ class ScreenRotationAnimation { /** Intensity of light/whiteness of the layout after rotation occurs. */ private float mEndLuma; - ScreenRotationAnimation(Context context, TransactionPool pool, - Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) { + ScreenRotationAnimation(Context context, TransactionPool pool, Transaction t, + TransitionInfo.Change change, SurfaceControl rootLeash, int animHint, int flags) { mContext = context; mTransactionPool = pool; mAnimHint = animHint; @@ -170,11 +175,20 @@ class ScreenRotationAnimation { } hardwareBuffer.close(); } + if ((flags & FLAG_HAS_WALLPAPER) != 0) { + mBackEffectSurface = new SurfaceControl.Builder() + .setCallsite("ShellRotationAnimation").setParent(rootLeash) + .setEffectLayer().setOpaque(true).setName("BackEffect").build(); + t.reparent(mSurfaceControl, mBackEffectSurface) + .setColor(mBackEffectSurface, + new float[] {mStartLuma, mStartLuma, mStartLuma}) + .show(mBackEffectSurface); + } t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE); t.show(mAnimLeash); // Crop the real content in case it contains a larger child layer, e.g. wallpaper. - t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight)); + t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight)); if (!isCustomRotate()) { mBackColorSurface = new SurfaceControl.Builder() @@ -202,6 +216,11 @@ class ScreenRotationAnimation { return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT; } + /** Returns the surface which contains the real content to animate enter. */ + private SurfaceControl getEnterSurface() { + return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl; + } + private void setScreenshotTransform(SurfaceControl.Transaction t) { if (mScreenshotLayer == null) { return; @@ -314,7 +333,11 @@ class ScreenRotationAnimation { } else { startDisplayRotation(animations, finishCallback, mainExecutor); startScreenshotRotationAnimation(animations, finishCallback, mainExecutor); - //startColorAnimation(mTransaction, animationScale); + if (mBackEffectSurface != null && mStartLuma > 0.1f) { + // Animate from the color of background to black for smooth alpha blending. + buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface, + animationScale, finishCallback, mainExecutor); + } } return true; @@ -322,7 +345,7 @@ class ScreenRotationAnimation { private void startDisplayRotation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { - buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback, + buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, null /* clipRect */, false /* isActivity */); } @@ -341,40 +364,17 @@ class ScreenRotationAnimation { null /* clipRect */, false /* isActivity */); } - private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) { - int colorTransitionMs = mContext.getResources().getInteger( - R.integer.config_screen_rotation_color_transition); - final float[] rgbTmpFloat = new float[3]; - final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma); - final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma); - final long duration = colorTransitionMs * (long) animationScale; - final Transaction t = mTransactionPool.acquire(); - - final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); - // Animation length is already expected to be scaled. - va.overrideDurationScale(1.0f); - va.setDuration(duration); - va.addUpdateListener(animation -> { - final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); - final float fraction = currentPlayTime / va.getDuration(); - applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t); - }); - va.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface, - t); - mTransactionPool.release(t); - } - - @Override - public void onAnimationEnd(Animator animation) { - applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface, - t); - mTransactionPool.release(t); - } - }); - animExecutor.execute(va::start); + private void buildLumaAnimation(@NonNull ArrayList<Animator> animations, + float startLuma, float endLuma, SurfaceControl surface, float animationScale, + @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { + final long durationMillis = (long) (mContext.getResources().getInteger( + R.integer.config_screen_rotation_color_transition) * animationScale); + final LumaAnimation animation = new LumaAnimation(durationMillis); + // Align the end with the enter animation. + animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis); + final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma); + DefaultSurfaceAnimator.buildSurfaceAnimation(animations, animation, finishCallback, + mTransactionPool, mainExecutor, adapter); } public void kill() { @@ -389,21 +389,47 @@ class ScreenRotationAnimation { if (mBackColorSurface != null && mBackColorSurface.isValid()) { t.remove(mBackColorSurface); } + if (mBackEffectSurface != null && mBackEffectSurface.isValid()) { + t.remove(mBackEffectSurface); + } t.apply(); mTransactionPool.release(t); } - private static void applyColor(int startColor, int endColor, float[] rgbFloat, - float fraction, SurfaceControl surface, SurfaceControl.Transaction t) { - final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor, - endColor); - Color middleColor = Color.valueOf(color); - rgbFloat[0] = middleColor.red(); - rgbFloat[1] = middleColor.green(); - rgbFloat[2] = middleColor.blue(); - if (surface.isValid()) { - t.setColor(surface, rgbFloat); + /** A no-op wrapper to provide animation duration. */ + private static class LumaAnimation extends Animation { + LumaAnimation(long durationMillis) { + setDuration(durationMillis); + } + } + + private static class LumaAnimationAdapter extends DefaultSurfaceAnimator.AnimationAdapter { + final float[] mColorArray = new float[3]; + final float mStartLuma; + final float mEndLuma; + final AccelerateInterpolator mInterpolation; + + LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) { + super(leash); + mStartLuma = startLuma; + mEndLuma = endLuma; + // Make the initial progress color lighter if the background is light. That avoids + // darker content when fading into the entering surface. + final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10); + Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor); + mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null; + } + + @Override + void applyTransformation(ValueAnimator animator, long currentPlayTime) { + final float fraction = mInterpolation != null + ? mInterpolation.getInterpolation(animator.getAnimatedFraction()) + : animator.getAnimatedFraction(); + final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma); + mColorArray[0] = luma; + mColorArray[1] = luma; + mColorArray[2] = luma; + mTransaction.setColor(mLeash, mColorArray); } - t.apply(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index d280dcd252b4..346f21b86e65 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -392,8 +392,6 @@ public class Transitions implements RemoteCallable<Transitions>, mShellCommandHandler.addCommandCallback("transitions", this, this); mShellCommandHandler.addDumpCallback(this::dump, this); - - registerObserver(mFocusTransitionObserver); } public boolean isRegistered() { @@ -1036,9 +1034,14 @@ public class Transitions implements RemoteCallable<Transitions>, * Gives every handler (in order) a chance to animate until one consumes the transition. * @return the handler which consumed the transition. */ - TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, - @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) { + public TransitionHandler dispatchTransition( + @NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull TransitionFinishCallback finishCB, + @Nullable TransitionHandler skip + ) { for (int i = mHandlers.size() - 1; i >= 0; --i) { if (mHandlers.get(i) == skip) continue; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index f5b2340b87a7..be4fd7c5eeec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -58,9 +58,11 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.shared.FocusTransitionListener; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; @@ -68,7 +70,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt; * View model for the window decoration with a caption and shadows. Works with * {@link CaptionWindowDecoration}. */ -public class CaptionWindowDecorViewModel implements WindowDecorViewModel { +public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusTransitionListener { private static final String TAG = "CaptionWindowDecorViewModel"; private final ShellTaskOrganizer mTaskOrganizer; @@ -85,6 +87,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final Region mExclusionRegion = Region.obtain(); private final InputManager mInputManager; private TaskOperations mTaskOperations; + private FocusTransitionObserver mFocusTransitionObserver; /** * Whether to pilfer the next motion event to send cancellations to the windows below. @@ -121,7 +124,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, - Transitions transitions) { + Transitions transitions, + FocusTransitionObserver focusTransitionObserver) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -133,6 +137,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSyncQueue = syncQueue; mTransitions = transitions; + mFocusTransitionObserver = focusTransitionObserver; if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskOperations = new TaskOperations(null, mContext, mSyncQueue); } @@ -148,6 +153,16 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } catch (RemoteException e) { Log.e(TAG, "Failed to register window manager callbacks", e); } + mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor); + } + + @Override + public void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay, + boolean isFocusedGlobally) { + final WindowDecoration decor = mWindowDecorByTaskId.get(taskId); + if (decor != null) { + decor.relayout(decor.mTaskInfo, isFocusedGlobally); + } } @Override @@ -180,7 +195,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { return; } - decoration.relayout(taskInfo); + decoration.relayout(taskInfo, decoration.mHasGlobalFocus); } @Override @@ -217,7 +232,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { createWindowDecoration(taskInfo, taskSurface, startT, finishT); } else { decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, - false /* setTaskCropAndPosition */); + false /* setTaskCropAndPosition */, + mFocusTransitionObserver.hasGlobalFocus(taskInfo)); } } @@ -230,7 +246,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) return; decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, - false /* setTaskCropAndPosition */); + false /* setTaskCropAndPosition */, + mFocusTransitionObserver.hasGlobalFocus(taskInfo)); } @Override @@ -308,7 +325,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { windowDecoration.setDragPositioningCallback(taskPositioner); windowDecoration.setTaskDragResizer(taskPositioner); windowDecoration.relayout(taskInfo, startT, finishT, - false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */); + false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */, + mFocusTransitionObserver.hasGlobalFocus(taskInfo)); } private class CaptionTouchEventListener implements @@ -359,9 +377,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } if (e.getAction() == MotionEvent.ACTION_DOWN) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); - if (!taskInfo.isFocused) { + if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reorder(mTaskToken, true /* onTop */); + wct.reorder(mTaskToken, true /* onTop */, true /* includingParents */); mSyncQueue.queue(wct); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 05065be7171c..509cb85c96cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -16,7 +16,7 @@ package com.android.wm.shell.windowdecor; -import static android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING; +import static android.window.DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; @@ -174,7 +174,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL } @Override - void relayout(RunningTaskInfo taskInfo) { + void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus) { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); // The crop and position of the task should only be set when a task is fluid resizing. In // all other cases, it is expected that the transition handler positions and crops the task @@ -185,7 +185,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL // synced with the buffer transaction (that draws the View). Both will be shown on screen // at the same, whereas applying them independently causes flickering. See b/270202228. relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */, - shouldSetTaskPositionAndCrop); + shouldSetTaskPositionAndCrop, hasGlobalFocus); } @VisibleForTesting @@ -194,16 +194,21 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL ActivityManager.RunningTaskInfo taskInfo, boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition, - InsetsState displayInsetsState) { + boolean isStatusBarVisible, + boolean isKeyguardVisibleAndOccluded, + InsetsState displayInsetsState, + boolean hasGlobalFocus) { relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; relayoutParams.mLayoutResId = R.layout.caption_window_decor; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); - relayoutParams.mShadowRadiusId = taskInfo.isFocused + relayoutParams.mShadowRadiusId = hasGlobalFocus ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition; + relayoutParams.mIsCaptionVisible = taskInfo.isFreeform() + || (isStatusBarVisible && !isKeyguardVisibleAndOccluded); if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // If the app is requesting to customize the caption bar, allow input to fall @@ -229,7 +234,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL @SuppressLint("MissingPermission") void relayout(RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) { + boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition, + boolean hasGlobalFocus) { final boolean isFreeform = taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; final boolean isDragResizeable = ENABLE_WINDOWING_SCALED_RESIZING.isTrue() @@ -240,7 +246,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final WindowContainerTransaction wct = new WindowContainerTransaction(); updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw, - setTaskCropAndPosition, mDisplayController.getInsetsState(taskInfo.displayId)); + setTaskCropAndPosition, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, + mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 272508f46d33..9e089b2460f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -22,9 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.content.Intent.ACTION_MAIN; -import static android.content.Intent.CATEGORY_APP_BROWSER; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_HOVER_ENTER; @@ -58,9 +55,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; -import android.net.Uri; import android.os.Handler; -import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; @@ -81,12 +76,13 @@ import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.ViewConfiguration; import android.widget.Toast; +import android.window.DesktopModeFlags; import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.flags.DesktopModeFlags; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.Cuj; @@ -107,12 +103,15 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler; import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; +import com.android.wm.shell.desktopmode.education.AppHandleEducationController; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.shared.FocusTransitionListener; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -125,6 +124,7 @@ import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; import com.android.wm.shell.windowdecor.extension.InsetsStateKt; @@ -134,6 +134,8 @@ import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import kotlin.Pair; import kotlin.Unit; +import kotlinx.coroutines.ExperimentalCoroutinesApi; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -145,7 +147,8 @@ import java.util.function.Supplier; * {@link DesktopModeWindowDecoration}. */ -public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { +public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, + FocusTransitionListener { private static final String TAG = "DesktopModeWindowDecorViewModel"; private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory; @@ -154,6 +157,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final ActivityTaskManager mActivityTaskManager; private final ShellCommandHandler mShellCommandHandler; private final ShellTaskOrganizer mTaskOrganizer; + private final DesktopRepository mDesktopRepository; private final ShellController mShellController; private final Context mContext; private final @ShellMainThread Handler mMainHandler; @@ -167,6 +171,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final MultiInstanceHelper mMultiInstanceHelper; private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository; private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter; + private final AppHandleEducationController mAppHandleEducationController; private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory; private boolean mTransitionDragActive; @@ -213,6 +218,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } }; private final TaskPositionerFactory mTaskPositionerFactory; + private final FocusTransitionObserver mFocusTransitionObserver; public DesktopModeWindowDecorViewModel( Context context, @@ -224,6 +230,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, + DesktopRepository desktopRepository, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -236,8 +243,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, + AppHandleEducationController appHandleEducationController, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, - Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler) { + Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, + FocusTransitionObserver focusTransitionObserver) { this( context, shellExecutor, @@ -248,6 +257,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { shellCommandHandler, windowManager, taskOrganizer, + desktopRepository, displayController, shellController, displayInsetsController, @@ -265,9 +275,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new SparseArray<>(), interactionJankMonitor, desktopTasksLimiter, + appHandleEducationController, windowDecorCaptionHandleRepository, activityOrientationChangeHandler, - new TaskPositionerFactory()); + new TaskPositionerFactory(), + focusTransitionObserver); } @VisibleForTesting @@ -281,6 +293,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, + DesktopRepository desktopRepository, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -298,9 +311,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId, InteractionJankMonitor interactionJankMonitor, Optional<DesktopTasksLimiter> desktopTasksLimiter, + AppHandleEducationController appHandleEducationController, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, - TaskPositionerFactory taskPositionerFactory) { + TaskPositionerFactory taskPositionerFactory, + FocusTransitionObserver focusTransitionObserver) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -308,6 +323,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mBgExecutor = bgExecutor; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; + mDesktopRepository = desktopRepository; mShellController = shellController; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; @@ -329,6 +345,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { com.android.internal.R.string.config_systemUi); mInteractionJankMonitor = interactionJankMonitor; mDesktopTasksLimiter = desktopTasksLimiter; + mAppHandleEducationController = appHandleEducationController; mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository; mActivityOrientationChangeHandler = activityOrientationChangeHandler; mAssistContentRequester = assistContentRequester; @@ -358,10 +375,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } }; mTaskPositionerFactory = taskPositionerFactory; + mFocusTransitionObserver = focusTransitionObserver; shellInit.addInitCallback(this::onInit, this); } + @OptIn(markerClass = ExperimentalCoroutinesApi.class) private void onInit() { mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener); mShellCommandHandler.addDumpCallback(this::dump, this); @@ -378,11 +397,34 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } catch (RemoteException e) { Log.e(TAG, "Failed to register window manager callbacks", e); } + if (DesktopModeStatus.canEnterDesktopMode(mContext) + && Flags.enableDesktopWindowingAppHandleEducation()) { + mAppHandleEducationController.setAppHandleEducationTooltipCallbacks( + /* appHandleTooltipClickCallback= */(taskId) -> { + openHandleMenu(taskId); + return Unit.INSTANCE; + }, + /* onToDesktopClickCallback= */(taskId, desktopModeTransitionSource) -> { + onToDesktop(taskId, desktopModeTransitionSource); + return Unit.INSTANCE; + }); + } + mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor); + } + + @Override + public void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay, + boolean isFocusedGlobally) { + final WindowDecoration decor = mWindowDecorByTaskId.get(taskId); + if (decor != null) { + decor.relayout(decor.mTaskInfo, isFocusedGlobally); + } } @Override public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) { mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue); + mDesktopTasksController.setFreeformTaskTransitionStarter(transitionStarter); } @Override @@ -423,7 +465,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo); + decoration.relayout(taskInfo, decoration.mHasGlobalFocus); mActivityOrientationChangeHandler.ifPresent(handler -> handler.handleActivityOrientationChange(oldTaskInfo, taskInfo)); } @@ -462,7 +504,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { createWindowDecoration(taskInfo, taskSurface, startT, finishT); } else { decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, - false /* shouldSetTaskPositionAndCrop */); + false /* shouldSetTaskPositionAndCrop */, + mFocusTransitionObserver.hasGlobalFocus(taskInfo)); } } @@ -475,7 +518,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) return; decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, - false /* shouldSetTaskPositionAndCrop */); + false /* shouldSetTaskPositionAndCrop */, + mFocusTransitionObserver.hasGlobalFocus(taskInfo)); } @Override @@ -495,6 +539,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mWindowDecorByTaskId.remove(taskInfo.taskId); } + private void openHandleMenu(int taskId) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo) + >= MANAGE_WINDOWS_MINIMUM_INSTANCES); + } + private void onMaximizeOrRestore(int taskId, String source) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) { @@ -508,6 +558,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { decoration.closeMaximizeMenu(); } + private void onEnterOrExitImmersive(int taskId) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + if (decoration == null) { + return; + } + mDesktopTasksController.toggleDesktopTaskFullImmersiveState(decoration.mTaskInfo); + } + private void onSnapResize(int taskId, boolean left) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) { @@ -532,20 +590,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { decoration.closeMaximizeMenu(); } - private void onOpenInBrowser(int taskId, @NonNull Uri uri) { + private void onOpenInBrowser(int taskId, @NonNull Intent intent) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) { return; } - openInBrowser(uri, decoration.getUser()); + openInBrowser(intent, decoration.getUser()); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); } - private void openInBrowser(Uri uri, @NonNull UserHandle userHandle) { - final Intent intent = Intent.makeMainSelectorActivity(ACTION_MAIN, CATEGORY_APP_BROWSER) - .setData(uri) - .addFlags(FLAG_ACTIVITY_NEW_TASK); + private void openInBrowser(@NonNull Intent intent, @NonNull UserHandle userHandle) { mContext.startActivityAsUser(intent, userHandle); } @@ -720,8 +775,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { if (!decoration.isHandleMenuActive()) { moveTaskToFront(decoration.mTaskInfo); - decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo) - >= MANAGE_WINDOWS_MINIMUM_INSTANCES); + openHandleMenu(mTaskId); } } else if (id == R.id.maximize_window) { // TODO(b/346441962): move click detection logic into the decor's @@ -729,13 +783,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // back to the decoration using // {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which // should shared with the maximize menu's maximize/restore actions. - onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button"); + if (Flags.enableFullyImmersiveInDesktop() + && TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) { + // Task is requesting immersive, so it should either enter or exit immersive, + // depending on immersive state. + onEnterOrExitImmersive(decoration.mTaskInfo.taskId); + } else { + // Full immersive is disabled or task doesn't request/support it, so just + // toggle between maximize/restore states. + onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button"); + } } else if (id == R.id.minimize_window) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - mDesktopTasksController.onDesktopWindowMinimize(wct, mTaskId); - final IBinder transition = mTaskOperations.minimizeTask(mTaskToken, wct); - mDesktopTasksLimiter.ifPresent(limiter -> - limiter.addPendingMinimizeChange(transition, mDisplayId, mTaskId)); + mDesktopTasksController.minimizeTask(decoration.mTaskInfo); } } @@ -852,7 +911,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } private void moveTaskToFront(RunningTaskInfo taskInfo) { - if (!taskInfo.isFocused) { + if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) { mDesktopTasksController.moveTaskToFront(taskInfo); } } @@ -909,14 +968,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window || id == R.id.open_menu_button || id == R.id.minimize_window); + final boolean dragAllowed = + !mDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId); switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { - mDragPointerId = e.getPointerId(0); - final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart( - 0 /* ctrlType */, e.getRawX(0), - e.getRawY(0)); - updateDragStatus(e.getActionMasked()); - mOnDragStartInitialBounds.set(initialBounds); + if (dragAllowed) { + mDragPointerId = e.getPointerId(0); + final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart( + 0 /* ctrlType */, e.getRawX(0), + e.getRawY(0)); + updateDragStatus(e.getActionMasked()); + mOnDragStartInitialBounds.set(initialBounds); + } mHasLongClicked = false; // Do not consume input event if a button is touched, otherwise it would // prevent the button's ripple effect from showing. @@ -925,6 +988,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case ACTION_MOVE: { // If a decor's resize drag zone is active, don't also try to reposition it. if (decoration.isHandlingDragResize()) break; + // Dragging the header isn't allowed, so skip the positioning work. + if (!dragAllowed) break; + decoration.closeMaximizeMenu(); if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); @@ -1010,6 +1076,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && action != MotionEvent.ACTION_CANCEL)) { return false; } + if (mDesktopRepository.isTaskInFullImmersiveState(mTaskId)) { + // Disallow double-tap to resize when in full immersive. + return false; + } onMaximizeOrRestore(mTaskId, "double_tap"); return true; } @@ -1394,6 +1464,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */), mDisplayController, mSplitScreenController, + mDesktopRepository, mTaskOrganizer, taskInfo, taskSurface, @@ -1445,8 +1516,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { onToSplitScreen(taskInfo.taskId); return Unit.INSTANCE; }); - windowDecoration.setOpenInBrowserClickListener((uri) -> { - onOpenInBrowser(taskInfo.taskId, uri); + windowDecoration.setOpenInBrowserClickListener((intent) -> { + onOpenInBrowser(taskInfo.taskId, intent); }); windowDecoration.setOnNewWindowClickListener(() -> { onNewWindow(taskInfo.taskId); @@ -1461,7 +1532,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { windowDecoration.setExclusionRegionListener(mExclusionRegionListener); windowDecoration.setDragPositioningCallback(taskPositioner); windowDecoration.relayout(taskInfo, startT, finishT, - false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */); + false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */, + mFocusTransitionObserver.hasGlobalFocus(taskInfo)); if (!Flags.enableHandleInputFix()) { incrementEventReceiverTasks(taskInfo.displayId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 8a53f5ba4a51..2c621b1f1a52 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -24,8 +24,8 @@ import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; -import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; -import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; +import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; +import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode; @@ -44,12 +44,14 @@ import android.app.WindowConfiguration.WindowingMode; import android.app.assist.AssistContent; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Insets; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -62,15 +64,17 @@ import android.os.UserHandle; import android.util.Size; import android.util.Slog; import android.view.Choreographer; +import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowInsets; import android.view.WindowManager; import android.widget.ImageButton; +import android.window.DesktopModeFlags; import android.window.TaskSnapshot; import android.window.WindowContainerTransaction; -import android.window.flags.DesktopModeFlags; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; @@ -83,12 +87,14 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; import com.android.wm.shell.apptoweb.AppToWebUtils; import com.android.wm.shell.apptoweb.AssistContentRequester; +import com.android.wm.shell.apptoweb.OpenByDefaultDialog; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -158,6 +164,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private MaximizeMenu mMaximizeMenu; + private OpenByDefaultDialog mOpenByDefaultDialog; + private ResizeVeil mResizeVeil; private Bitmap mAppIconBitmap; private Bitmap mResizeVeilBitmap; @@ -166,7 +174,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private CapturedLink mCapturedLink; private Uri mGenericLink; private Uri mWebUri; - private Consumer<Uri> mOpenInBrowserClickListener; + private Consumer<Intent> mOpenInBrowserClickListener; private ExclusionRegionListener mExclusionRegionListener; @@ -188,12 +196,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired; private final MultiInstanceHelper mMultiInstanceHelper; private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository; + private final DesktopRepository mDesktopRepository; DesktopModeWindowDecoration( Context context, @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, + DesktopRepository desktopRepository, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -207,8 +217,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository) { - this (context, userContext, displayController, splitScreenController, taskOrganizer, - taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, + this (context, userContext, displayController, splitScreenController, desktopRepository, + taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, @@ -225,6 +235,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, + DesktopRepository desktopRepository, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -264,6 +275,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mMultiInstanceHelper = multiInstanceHelper; mWindowManagerWrapper = windowManagerWrapper; mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository; + mDesktopRepository = desktopRepository; } /** @@ -335,12 +347,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mDragPositioningCallback = dragPositioningCallback; } - void setOpenInBrowserClickListener(Consumer<Uri> listener) { + void setOpenInBrowserClickListener(Consumer<Intent> listener) { mOpenInBrowserClickListener = listener; } @Override - void relayout(ActivityManager.RunningTaskInfo taskInfo) { + void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) { final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); // The crop and position of the task should only be set when a task is fluid resizing. In // all other cases, it is expected that the transition handler positions and crops the task @@ -353,7 +365,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // the View). Both will be shown on screen at the same, whereas applying them independently // causes flickering. See b/270202228. final boolean applyTransactionOnDraw = taskInfo.isFreeform(); - relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop); + relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop, + hasGlobalFocus); if (!applyTransactionOnDraw) { t.apply(); } @@ -361,18 +374,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { + boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop, + boolean hasGlobalFocus) { Trace.beginSection("DesktopModeWindowDecoration#relayout"); if (taskInfo.isFreeform()) { // The Task is in Freeform mode -> show its header in sync since it's an integral part // of the window itself - a delayed header might cause bad UX. relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); + shouldSetTaskPositionAndCrop, hasGlobalFocus); } else { // The Task is outside Freeform mode -> allow the handle view to be delayed since the // handle is just a small addition to the window. relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); + shouldSetTaskPositionAndCrop, hasGlobalFocus); } Trace.endSection(); } @@ -380,11 +394,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /** Run the whole relayout phase immediately without delay. */ private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { + boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop, + boolean hasGlobalFocus) { // Clear the current ViewHost runnable as we will update the ViewHost here clearCurrentViewHostRunnable(); updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); + shouldSetTaskPositionAndCrop, hasGlobalFocus); if (mResult.mRootView != null) { updateViewHost(mRelayoutParams, startT, mResult); } @@ -406,7 +421,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { + boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop, + boolean hasGlobalFocus) { if (applyStartTransactionOnDraw) { throw new IllegalArgumentException( "We cannot both sync viewhost ondraw and delay viewhost creation."); @@ -414,7 +430,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // Clear the current ViewHost runnable as we will update the ViewHost here clearCurrentViewHostRunnable(); updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, - false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop); + false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop, + hasGlobalFocus); if (mResult.mRootView == null) { // This means something blocks the window decor from showing, e.g. the task is hidden. // Nothing is set up in this case including the decoration surface. @@ -428,7 +445,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @SuppressLint("MissingPermission") private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { + boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop, + boolean hasGlobalFocus) { Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces"); if (Flags.enableDesktopWindowingAppToWeb()) { @@ -439,8 +457,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandleMenu.relayout(startT, mResult.mCaptionX); } + if (isOpenByDefaultDialogActive()) { + mOpenByDefaultDialog.relayout(taskInfo); + } + + final boolean inFullImmersive = mDesktopRepository + .isTaskInFullImmersiveState(taskInfo.taskId); updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); + shouldSetTaskPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, + inFullImmersive, mDisplayController.getInsetsState(taskInfo.displayId), + hasGlobalFocus); final WindowDecorLinearLayout oldRootView = mResult.mRootView; final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; @@ -461,6 +487,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) { notifyNoCaptionHandle(); } + mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeStatusBarInputLayer(); Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces return; @@ -479,20 +506,27 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) { notifyCaptionStateChanged(); } - mWindowDecorViewHolder.bindData(mTaskInfo, - position, - mResult.mCaptionWidth, - mResult.mCaptionHeight, - isCaptionVisible()); + + if (isAppHandle(mWindowDecorViewHolder)) { + mWindowDecorViewHolder.bindData(new AppHandleViewHolder.HandleData( + mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight, + isCaptionVisible() + )); + } else { + mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData( + mTaskInfo, TaskInfoKt.getRequestingImmersive(mTaskInfo), inFullImmersive, + hasGlobalFocus + )); + } Trace.endSection(); - if (!mTaskInfo.isFocused) { + if (!hasGlobalFocus) { closeHandleMenu(); closeManageWindowsMenu(); closeMaximizeMenu(); } - updateDragResizeListener(oldDecorationSurface); - updateMaximizeMenu(startT); + updateDragResizeListener(oldDecorationSurface, inFullImmersive); + updateMaximizeMenu(startT, inFullImmersive); Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces } @@ -517,32 +551,40 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } @Nullable - private Uri getBrowserLink() { + private Intent getBrowserLink() { // Do not show browser link in browser applications final ComponentName baseActivity = mTaskInfo.baseActivity; if (baseActivity != null && AppToWebUtils.isBrowserApp(mContext, baseActivity.getPackageName(), mUserContext.getUserId())) { return null; } + + final Uri browserLink; // If the captured link is available and has not expired, return the captured link. // Otherwise, return the generic link which is set to null if a generic link is unavailable. if (mCapturedLink != null && !mCapturedLink.mExpired) { - return mCapturedLink.mUri; + browserLink = mCapturedLink.mUri; } else if (mWebUri != null) { - return mWebUri; + browserLink = mWebUri; + } else { + browserLink = mGenericLink; } - return mGenericLink; + + if (browserLink == null) return null; + return AppToWebUtils.getBrowserIntent(browserLink, mContext.getPackageManager()); + } UserHandle getUser() { return mUserContext.getUser(); } - private void updateDragResizeListener(SurfaceControl oldDecorationSurface) { - if (!isDragResizable(mTaskInfo)) { + private void updateDragResizeListener(SurfaceControl oldDecorationSurface, + boolean inFullImmersive) { + if (!isDragResizable(mTaskInfo, inFullImmersive)) { if (!mTaskInfo.positionInParent.equals(mPositionInParent)) { // We still want to track caption bar's exclusion region on a non-resizeable task. - updateExclusionRegion(); + updateExclusionRegion(inFullImmersive); } closeDragResizeListener(); return; @@ -576,11 +618,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res), getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop) || !mTaskInfo.positionInParent.equals(mPositionInParent)) { - updateExclusionRegion(); + updateExclusionRegion(inFullImmersive); } } - private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) { + private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo, + boolean inFullImmersive) { + if (inFullImmersive) { + // Task cannot be resized in full immersive. + return false; + } if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue()) { return taskInfo.isFreeform(); } @@ -644,8 +691,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState); } - private void updateMaximizeMenu(SurfaceControl.Transaction startT) { - if (!isDragResizable(mTaskInfo) || !isMaximizeMenuActive()) { + private void updateMaximizeMenu(SurfaceControl.Transaction startT, boolean inFullImmersive) { + if (!isDragResizable(mTaskInfo, inFullImmersive) || !isMaximizeMenuActive()) { return; } if (!mTaskInfo.isVisible()) { @@ -737,7 +784,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Context context, ActivityManager.RunningTaskInfo taskInfo, boolean applyStartTransactionOnDraw, - boolean shouldSetTaskPositionAndCrop) { + boolean shouldSetTaskPositionAndCrop, + boolean isStatusBarVisible, + boolean isKeyguardVisibleAndOccluded, + boolean inFullImmersiveMode, + @NonNull InsetsState displayInsetsState, + boolean hasGlobalFocus) { final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode()); final boolean isAppHeader = captionLayoutId == R.layout.desktop_mode_app_header; @@ -747,7 +799,30 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mLayoutResId = captionLayoutId; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); + relayoutParams.mHasGlobalFocus = hasGlobalFocus; + final boolean showCaption; + if (Flags.enableFullyImmersiveInDesktop()) { + if (inFullImmersiveMode) { + showCaption = isStatusBarVisible && !isKeyguardVisibleAndOccluded; + } else { + showCaption = taskInfo.isFreeform() + || (isStatusBarVisible && !isKeyguardVisibleAndOccluded); + } + } else { + // Caption should always be visible in freeform mode. When not in freeform, + // align with the status bar except when showing over keyguard (where it should not + // shown). + // TODO(b/356405803): Investigate how it's possible for the status bar visibility to + // be false while a freeform window is open if the status bar is always + // forcibly-shown. It may be that the InsetsState (from which |mIsStatusBarVisible| + // is set) still contains an invisible insets source in immersive cases even if the + // status bar is shown? + showCaption = taskInfo.isFreeform() + || (isStatusBarVisible && !isKeyguardVisibleAndOccluded); + } + relayoutParams.mIsCaptionVisible = showCaption; + relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode; if (isAppHeader) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // If the app is requesting to customize the caption bar, allow input to fall @@ -766,6 +841,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // including non-immersive apps that just don't handle caption insets properly. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; } + if (Flags.enableFullyImmersiveInDesktop() && inFullImmersiveMode) { + final Insets systemBarInsets = displayInsetsState.calculateInsets( + taskInfo.getConfiguration().windowConfiguration.getBounds(), + WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(), + false /* ignoreVisibility */); + relayoutParams.mCaptionTopPadding = systemBarInsets.top; + } // Report occluding elements as bounding rects to the insets system so that apps can // draw in the empty space in the center: // First, the "app chip" section of the caption bar (+ some extra margins). @@ -792,8 +874,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; } - if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) { - relayoutParams.mShadowRadiusId = taskInfo.isFocused + if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) { + relayoutParams.mShadowRadiusId = hasGlobalFocus ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; } @@ -880,6 +962,33 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return mHandleMenu != null; } + boolean isOpenByDefaultDialogActive() { + return mOpenByDefaultDialog != null; + } + + void createOpenByDefaultDialog() { + mOpenByDefaultDialog = new OpenByDefaultDialog( + mContext, + mTaskInfo, + mTaskSurface, + mDisplayController, + mSurfaceControlTransactionSupplier, + new OpenByDefaultDialog.DialogLifecycleListener() { + @Override + public void onDialogCreated() { + closeHandleMenu(); + } + + @Override + public void onDialogDismissed() { + mOpenByDefaultDialog = null; + } + }, + mAppIconBitmap, + mAppName + ); + } + boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) { return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset); } @@ -1048,7 +1157,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** - * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs. + * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.fmdra */ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) { return stableBounds.bottom - requiredEmptySpace; @@ -1171,11 +1280,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener, /* onNewWindowClickListener= */ mOnNewWindowClickListener, /* onManageWindowsClickListener= */ mOnManageWindowsClickListener, - /* openInBrowserClickListener= */ (uri) -> { - mOpenInBrowserClickListener.accept(uri); + /* openInBrowserClickListener= */ (intent) -> { + mOpenInBrowserClickListener.accept(intent); onCapturedLinkExpired(); return Unit.INSTANCE; }, + /* onOpenByDefaultClickListener= */ () -> { + if (!isOpenByDefaultDialogActive()) { + createOpenByDefaultDialog(); + } + return Unit.INSTANCE; + }, /* onCloseMenuClickListener= */ () -> { closeHandleMenu(); return Unit.INSTANCE; @@ -1302,7 +1417,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } boolean isFocused() { - return mTaskInfo.isFocused; + return mHasGlobalFocus; } /** @@ -1446,24 +1561,29 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mPositionInParent.set(mTaskInfo.positionInParent); } - private void updateExclusionRegion() { + private void updateExclusionRegion(boolean inFullImmersive) { // An outdated position in parent is one reason for this to be called; update it here. updatePositionInParent(); mExclusionRegionListener - .onExclusionRegionChanged(mTaskInfo.taskId, getGlobalExclusionRegion()); + .onExclusionRegionChanged(mTaskInfo.taskId, + getGlobalExclusionRegion(inFullImmersive)); } /** * Create a new exclusion region from the corner rects (if resizeable) and caption bounds * of this task. */ - private Region getGlobalExclusionRegion() { + private Region getGlobalExclusionRegion(boolean inFullImmersive) { Region exclusionRegion; - if (mDragResizeListener != null && isDragResizable(mTaskInfo)) { + if (mDragResizeListener != null && isDragResizable(mTaskInfo, inFullImmersive)) { exclusionRegion = mDragResizeListener.getCornersRegion(); } else { exclusionRegion = new Region(); } + if (inFullImmersive) { + // Task can't be moved in full immersive, so skip excluding the caption region. + return exclusionRegion; + } exclusionRegion.union(new Rect(0, 0, mResult.mWidth, getCaptionHeight(mTaskInfo.getWindowingMode()))); exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y); @@ -1481,7 +1601,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) { return windowingMode == WINDOWING_MODE_FULLSCREEN - ? R.dimen.desktop_mode_fullscreen_decor_caption_height + ? com.android.internal.R.dimen.status_bar_height_default : R.dimen.desktop_mode_freeform_decor_caption_height; } @@ -1531,6 +1651,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, + DesktopRepository desktopRepository, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -1549,6 +1670,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin userContext, displayController, splitScreenController, + desktopRepository, taskOrganizer, taskInfo, taskSurface, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java index 38f9cfaca7ae..60c922293d80 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java @@ -27,7 +27,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.SurfaceControl; -import android.window.flags.DesktopModeFlags; +import android.window.DesktopModeFlags; import androidx.annotation.NonNull; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java index d726f5083eb6..33d1c260cb84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java @@ -18,7 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_MOUSE; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; -import static android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_EDGE_DRAG_RESIZE; +import static android.window.DesktopModeFlags.ENABLE_WINDOWING_EDGE_DRAG_RESIZE; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 3853f1f086c1..ccf329c2bb22 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -93,9 +93,10 @@ class FluidResizeTaskPositioner implements TaskPositioner, Transitions.Transitio mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); mRepositionStartPoint.set(x, y); mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId); - if (mCtrlType != CTRL_TYPE_UNDEFINED && !mWindowDecoration.mTaskInfo.isFocused) { + if (mCtrlType != CTRL_TYPE_UNDEFINED && !mWindowDecoration.mHasGlobalFocus) { WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reorder(mWindowDecoration.mTaskInfo.token, true); + wct.reorder(mWindowDecoration.mTaskInfo.token, true /* onTop */, + true /* includingParents */); mTaskOrganizer.applyTransaction(wct); } mRepositionTaskBounds.set(mTaskBoundsAtDragStart); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index 9a5b4f54dd36..2e327035bddf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -20,13 +20,13 @@ import android.annotation.DimenRes import android.annotation.SuppressLint import android.app.ActivityManager.RunningTaskInfo import android.content.Context +import android.content.Intent import android.content.res.ColorStateList import android.content.res.Resources import android.graphics.Bitmap import android.graphics.Point import android.graphics.PointF import android.graphics.Rect -import android.net.Uri import android.view.LayoutInflater import android.view.MotionEvent import android.view.MotionEvent.ACTION_OUTSIDE @@ -70,7 +70,7 @@ class HandleMenu( private val shouldShowWindowingPill: Boolean, private val shouldShowNewWindowButton: Boolean, private val shouldShowManageWindowsButton: Boolean, - private val openInBrowserLink: Uri?, + private val openInBrowserIntent: Intent?, private val captionWidth: Int, private val captionHeight: Int, captionX: Int @@ -107,7 +107,7 @@ class HandleMenu( private val globalMenuPosition: Point = Point() private val shouldShowBrowserPill: Boolean - get() = openInBrowserLink != null + get() = openInBrowserIntent != null init { updateHandleMenuPillPositions(captionX) @@ -119,7 +119,8 @@ class HandleMenu( onToSplitScreenClickListener: () -> Unit, onNewWindowClickListener: () -> Unit, onManageWindowsClickListener: () -> Unit, - openInBrowserClickListener: (Uri) -> Unit, + openInBrowserClickListener: (Intent) -> Unit, + onOpenByDefaultClickListener: () -> Unit, onCloseMenuClickListener: () -> Unit, onOutsideTouchListener: () -> Unit, ) { @@ -135,6 +136,7 @@ class HandleMenu( onNewWindowClickListener = onNewWindowClickListener, onManageWindowsClickListener = onManageWindowsClickListener, openInBrowserClickListener = openInBrowserClickListener, + onOpenByDefaultClickListener = onOpenByDefaultClickListener, onCloseMenuClickListener = onCloseMenuClickListener, onOutsideTouchListener = onOutsideTouchListener, ) @@ -152,7 +154,8 @@ class HandleMenu( onToSplitScreenClickListener: () -> Unit, onNewWindowClickListener: () -> Unit, onManageWindowsClickListener: () -> Unit, - openInBrowserClickListener: (Uri) -> Unit, + openInBrowserClickListener: (Intent) -> Unit, + onOpenByDefaultClickListener: () -> Unit, onCloseMenuClickListener: () -> Unit, onOutsideTouchListener: () -> Unit ) { @@ -172,8 +175,9 @@ class HandleMenu( this.onNewWindowClickListener = onNewWindowClickListener this.onManageWindowsClickListener = onManageWindowsClickListener this.onOpenInBrowserClickListener = { - openInBrowserClickListener.invoke(openInBrowserLink!!) + openInBrowserClickListener.invoke(openInBrowserIntent!!) } + this.onOpenByDefaultClickListener = onOpenByDefaultClickListener this.onCloseMenuClickListener = onCloseMenuClickListener this.onOutsideTouchListener = onOutsideTouchListener } @@ -448,7 +452,8 @@ class HandleMenu( private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill) private val browserBtn = openInBrowserPill.requireViewById<Button>( R.id.open_in_browser_button) - + private val openByDefaultBtn = openInBrowserPill.requireViewById<ImageButton>( + R.id.open_by_default_button) private val decorThemeUtil = DecorThemeUtil(context) private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat()) @@ -461,6 +466,7 @@ class HandleMenu( var onNewWindowClickListener: (() -> Unit)? = null var onManageWindowsClickListener: (() -> Unit)? = null var onOpenInBrowserClickListener: (() -> Unit)? = null + var onOpenByDefaultClickListener: (() -> Unit)? = null var onCloseMenuClickListener: (() -> Unit)? = null var onOutsideTouchListener: (() -> Unit)? = null @@ -469,6 +475,9 @@ class HandleMenu( splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() } desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() } browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() } + openByDefaultBtn.setOnClickListener { + onOpenByDefaultClickListener?.invoke() + } collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() } newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() } manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() } @@ -634,6 +643,8 @@ class HandleMenu( setTextColor(style.textColor) compoundDrawableTintList = ColorStateList.valueOf(style.textColor) } + + openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor) } private data class MenuStyle( @@ -661,7 +672,7 @@ interface HandleMenuFactory { shouldShowWindowingPill: Boolean, shouldShowNewWindowButton: Boolean, shouldShowManageWindowsButton: Boolean, - openInBrowserLink: Uri?, + openInBrowserIntent: Intent?, captionWidth: Int, captionHeight: Int, captionX: Int @@ -680,7 +691,7 @@ object DefaultHandleMenuFactory : HandleMenuFactory { shouldShowWindowingPill: Boolean, shouldShowNewWindowButton: Boolean, shouldShowManageWindowsButton: Boolean, - openInBrowserLink: Uri?, + openInBrowserIntent: Intent?, captionWidth: Int, captionHeight: Int, captionX: Int @@ -695,7 +706,7 @@ object DefaultHandleMenuFactory : HandleMenuFactory { shouldShowWindowingPill, shouldShowNewWindowButton, shouldShowManageWindowsButton, - openInBrowserLink, + openInBrowserIntent, captionWidth, captionHeight, captionX diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt index 2d97dc06cf89..376cd2a78baf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator +import android.annotation.DrawableRes import android.content.Context import android.content.res.ColorStateList import android.graphics.Color @@ -32,7 +33,7 @@ import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import androidx.core.content.ContextCompat import com.android.wm.shell.R -import android.window.flags.DesktopModeFlags +import android.window.DesktopModeFlags private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350 private const val MAX_DRAWABLE_ALPHA = 255 @@ -132,6 +133,11 @@ class MaximizeButtonView( } } + /** Set the drawable resource to use for the maximize button. */ + fun setIcon(@DrawableRes icon: Int) { + maximizeWindow.setImageResource(icon) + } + companion object { private const val OPACITY_15 = 38 } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 6eb5cca9ad1a..ff3b45555bce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -106,9 +106,10 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T // Capture CUJ for re-sizing window in DW mode. mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW); - if (!mDesktopWindowDecoration.mTaskInfo.isFocused) { + if (!mDesktopWindowDecoration.mHasGlobalFocus) { WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true); + wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true /* onTop */, + true /* includingParents */); mTaskOrganizer.applyTransaction(wct); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index c1a55b48a02a..ce5cfd0bdc36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -125,7 +125,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } mDisplayController.removeDisplayWindowListener(this); - relayout(mTaskInfo); + relayout(mTaskInfo, mHasGlobalFocus); } }; @@ -144,8 +144,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> TaskDragResizer mTaskDragResizer; boolean mIsCaptionVisible; - private boolean mIsStatusBarVisible; - private boolean mIsKeyguardVisibleAndOccluded; + boolean mIsStatusBarVisible; + boolean mIsKeyguardVisibleAndOccluded; + boolean mHasGlobalFocus; /** The most recent set of insets applied to this window decoration. */ private WindowDecorationInsets mWindowDecorationInsets; @@ -199,8 +200,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> * * @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the * constructor. + * @param hasGlobalFocus Whether the task is focused */ - abstract void relayout(RunningTaskInfo taskInfo); + abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus); /** * Used by the {@link DragPositioningCallback} associated with the implementing class to @@ -225,6 +227,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> if (params.mRunningTaskInfo != null) { mTaskInfo = params.mRunningTaskInfo; } + mHasGlobalFocus = params.mHasGlobalFocus; final int oldLayoutResId = mLayoutResId; mLayoutResId = params.mLayoutResId; @@ -241,12 +244,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } rootView = null; // Clear it just in case we use it accidentally - updateCaptionVisibility(outResult.mRootView); + updateCaptionVisibility(outResult.mRootView, params); final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds(); outResult.mWidth = taskBounds.width(); outResult.mHeight = taskBounds.height(); - outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused); + outResult.mRootView.setTaskFocusState(mHasGlobalFocus); final Resources resources = mDecorWindowContext.getResources(); outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId) + params.mCaptionTopPadding; @@ -346,7 +349,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct, RelayoutResult<T> outResult, Rect taskBounds) { - if (!mIsCaptionVisible) { + if (!mIsCaptionVisible || !params.mIsInsetSource) { if (mWindowDecorationInsets != null) { mWindowDecorationInsets.remove(wct); mWindowDecorationInsets = null; @@ -391,11 +394,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final WindowDecorationInsets newInsets = new WindowDecorationInsets( mTaskInfo.token, mOwner, captionInsetsRect, boundingRects, - params.mInsetSourceFlags); + params.mInsetSourceFlags, params.mIsInsetSource); if (!newInsets.equals(mWindowDecorationInsets)) { // Add or update this caption as an insets source. mWindowDecorationInsets = newInsets; - mWindowDecorationInsets.addOrUpdate(wct); + mWindowDecorationInsets.update(wct); } } @@ -512,7 +515,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mIsKeyguardVisibleAndOccluded = visible && occluded; final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded; if (changed) { - relayout(mTaskInfo); + relayout(mTaskInfo, mHasGlobalFocus); } } @@ -522,22 +525,15 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible; if (changed) { - relayout(mTaskInfo); + relayout(mTaskInfo, mHasGlobalFocus); } } /** - * Checks if task has entered/exited immersive mode and requires a change in caption visibility. + * Update caption visibility state and views. */ - private void updateCaptionVisibility(View rootView) { - // Caption should always be visible in freeform mode. When not in freeform, align with the - // status bar except when showing over keyguard (where it should not shown). - // TODO(b/356405803): Investigate how it's possible for the status bar visibility to be - // false while a freeform window is open if the status bar is always forcibly-shown. It - // may be that the InsetsState (from which |mIsStatusBarVisible| is set) still contains - // an invisible insets source in immersive cases even if the status bar is shown? - mIsCaptionVisible = mTaskInfo.isFreeform() - || (mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded); + private void updateCaptionVisibility(View rootView, @NonNull RelayoutParams params) { + mIsCaptionVisible = params.mIsCaptionVisible; setCaptionVisibility(rootView, mIsCaptionVisible); } @@ -717,10 +713,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId); final Rect captionInsets = new Rect(0, 0, 0, captionHeight); final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token, - mOwner, captionInsets, null /* boundingRets */, 0 /* flags */); + mOwner, captionInsets, null /* boundingRets */, 0 /* flags */, + true /* shouldAddCaptionInset */); if (!newInsets.equals(mWindowDecorationInsets)) { mWindowDecorationInsets = newInsets; - mWindowDecorationInsets.addOrUpdate(wct); + mWindowDecorationInsets.update(wct); } } @@ -731,17 +728,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mCaptionWidthId; final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>(); int mInputFeatures; + boolean mIsInsetSource = true; @InsetsSource.Flags int mInsetSourceFlags; int mShadowRadiusId; int mCornerRadius; int mCaptionTopPadding; + boolean mIsCaptionVisible; Configuration mWindowDecorConfig; boolean mApplyStartTransactionOnDraw; boolean mSetTaskPositionAndCrop; + boolean mHasGlobalFocus; void reset() { mLayoutResId = Resources.ID_NULL; @@ -749,16 +749,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionWidthId = Resources.ID_NULL; mOccludingCaptionElements.clear(); mInputFeatures = 0; + mIsInsetSource = true; mInsetSourceFlags = 0; mShadowRadiusId = Resources.ID_NULL; mCornerRadius = 0; mCaptionTopPadding = 0; + mIsCaptionVisible = false; mApplyStartTransactionOnDraw = false; mSetTaskPositionAndCrop = false; mWindowDecorConfig = null; + mHasGlobalFocus = false; } boolean hasInputFeatureSpy() { @@ -817,21 +820,26 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private final Rect mFrame; private final Rect[] mBoundingRects; private final @InsetsSource.Flags int mFlags; + private final boolean mShouldAddCaptionInset; private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame, - Rect[] boundingRects, @InsetsSource.Flags int flags) { + Rect[] boundingRects, @InsetsSource.Flags int flags, + boolean shouldAddCaptionInset) { mToken = token; mOwner = owner; mFrame = frame; mBoundingRects = boundingRects; mFlags = flags; + mShouldAddCaptionInset = shouldAddCaptionInset; } - void addOrUpdate(WindowContainerTransaction wct) { - wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects, - mFlags); - wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame, - mBoundingRects, 0 /* flags */); + void update(WindowContainerTransaction wct) { + if (mShouldAddCaptionInset) { + wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects, + mFlags); + wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame, + mBoundingRects, 0 /* flags */); + } } void remove(WindowContainerTransaction wct) { @@ -846,7 +854,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner, that.mOwner) && Objects.equals(mFrame, that.mFrame) && Objects.deepEquals(mBoundingRects, that.mBoundingRects) - && mFlags == that.mFlags; + && mFlags == that.mFlags + && mShouldAddCaptionInset == that.mShouldAddCaptionInset; } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt index 226b0fb2e1a1..1be26f080ac8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt @@ -107,4 +107,27 @@ class AdditionalSystemViewContainer( } windowManagerWrapper.updateViewLayout(view, lp) } + + class Factory { + fun create( + windowManagerWrapper: WindowManagerWrapper, + taskId: Int, + x: Int, + y: Int, + width: Int, + height: Int, + flags: Int, + view: View, + ): AdditionalSystemViewContainer = + AdditionalSystemViewContainer( + windowManagerWrapper = windowManagerWrapper, + taskId = taskId, + x = x, + y = y, + width = width, + height = height, + flags = flags, + view = view + ) + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt new file mode 100644 index 000000000000..c61b31e7ba01 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2024 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.windowdecor.education + +import android.annotation.ColorInt +import android.annotation.DimenRes +import android.annotation.LayoutRes +import android.content.Context +import android.content.res.Resources +import android.graphics.Point +import android.util.Size +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.View.MeasureSpec.UNSPECIFIED +import android.view.WindowManager +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import android.window.DisplayAreaInfo +import android.window.WindowContainerTransaction +import androidx.core.graphics.drawable.DrawableCompat +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce +import com.android.wm.shell.R +import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.shared.animation.PhysicsAnimator +import com.android.wm.shell.windowdecor.WindowManagerWrapper +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer + +/** + * Controls the lifecycle of an education tooltip, including showing and hiding it. Ensures that + * only one tooltip is displayed at a time. + */ +class DesktopWindowingEducationTooltipController( + private val context: Context, + private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory, + private val displayController: DisplayController, +) : OnDisplayChangingListener { + // TODO: b/369384567 - Set tooltip color scheme to match LT/DT of app theme + private var tooltipView: View? = null + private var animator: PhysicsAnimator<View>? = null + private val springConfig by lazy { + PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY) + } + private var popupWindow: AdditionalSystemViewContainer? = null + + override fun onDisplayChange( + displayId: Int, + fromRotation: Int, + toRotation: Int, + newDisplayAreaInfo: DisplayAreaInfo?, + t: WindowContainerTransaction? + ) { + // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and + // [toRotation] can be one of the [@Surface.Rotation] values. + if ((fromRotation % 2 == toRotation % 2)) return + hideEducationTooltip() + // TODO: b/370820018 - Update tooltip position on orientation change instead of dismissing + } + + /** + * Shows education tooltip. + * + * @param tooltipViewConfig features of tooltip. + * @param taskId is used in the title of popup window created for the tooltip view. + */ + fun showEducationTooltip(tooltipViewConfig: EducationViewConfig, taskId: Int) { + hideEducationTooltip() + tooltipView = createEducationTooltipView(tooltipViewConfig, taskId) + animator = createAnimator() + animateShowTooltipTransition() + displayController.addDisplayChangingController(this) + } + + /** Hide the current education view if visible */ + private fun hideEducationTooltip() = animateHideTooltipTransition { cleanUp() } + + /** Create education view by inflating layout provided. */ + private fun createEducationTooltipView( + tooltipViewConfig: EducationViewConfig, + taskId: Int, + ): View { + val tooltipView = + LayoutInflater.from(context) + .inflate( + tooltipViewConfig.tooltipViewLayout, /* root= */ null, /* attachToRoot= */ false) + .apply { + alpha = 0f + scaleX = 0f + scaleY = 0f + + requireViewById<TextView>(R.id.tooltip_text).apply { + text = tooltipViewConfig.tooltipText + } + + setOnTouchListener { _, motionEvent -> + if (motionEvent.action == MotionEvent.ACTION_OUTSIDE) { + hideEducationTooltip() + tooltipViewConfig.onDismissAction() + true + } else { + false + } + } + setOnClickListener { + hideEducationTooltip() + tooltipViewConfig.onEducationClickAction() + } + setTooltipColorScheme(tooltipViewConfig.tooltipColorScheme) + } + + val tooltipDimens = tooltipDimens(tooltipView = tooltipView, tooltipViewConfig.arrowDirection) + val tooltipViewGlobalCoordinates = + tooltipViewGlobalCoordinates( + tooltipViewGlobalCoordinates = tooltipViewConfig.tooltipViewGlobalCoordinates, + arrowDirection = tooltipViewConfig.arrowDirection, + tooltipDimen = tooltipDimens) + createTooltipPopupWindow( + taskId, tooltipViewGlobalCoordinates, tooltipDimens, tooltipView = tooltipView) + + return tooltipView + } + + /** Create animator for education transitions */ + private fun createAnimator(): PhysicsAnimator<View>? = + tooltipView?.let { + PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) } + } + + /** Animate show transition for the education view */ + private fun animateShowTooltipTransition() { + animator + ?.spring(DynamicAnimation.ALPHA, 1f) + ?.spring(DynamicAnimation.SCALE_X, 1f) + ?.spring(DynamicAnimation.SCALE_Y, 1f) + ?.start() + } + + /** Animate hide transition for the education view */ + private fun animateHideTooltipTransition(endActions: () -> Unit) { + animator + ?.spring(DynamicAnimation.ALPHA, 0f) + ?.spring(DynamicAnimation.SCALE_X, 0f) + ?.spring(DynamicAnimation.SCALE_Y, 0f) + ?.start() + endActions() + } + + /** Remove education tooltip and clean up all relative properties */ + private fun cleanUp() { + tooltipView = null + animator = null + popupWindow?.releaseView() + popupWindow = null + displayController.removeDisplayChangingController(this) + } + + private fun createTooltipPopupWindow( + taskId: Int, + tooltipViewGlobalCoordinates: Point, + tooltipDimen: Size, + tooltipView: View, + ) { + popupWindow = + additionalSystemViewContainerFactory.create( + windowManagerWrapper = + WindowManagerWrapper(context.getSystemService(WindowManager::class.java)), + taskId = taskId, + x = tooltipViewGlobalCoordinates.x, + y = tooltipViewGlobalCoordinates.y, + width = tooltipDimen.width, + height = tooltipDimen.height, + flags = + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, + view = tooltipView) + } + + private fun View.setTooltipColorScheme(tooltipColorScheme: TooltipColorScheme) { + requireViewById<LinearLayout>(R.id.tooltip_container).apply { + background.setTint(tooltipColorScheme.container) + } + requireViewById<ImageView>(R.id.arrow_icon).apply { + val wrappedDrawable = DrawableCompat.wrap(this.drawable) + DrawableCompat.setTint(wrappedDrawable, tooltipColorScheme.container) + } + requireViewById<TextView>(R.id.tooltip_text).apply { setTextColor(tooltipColorScheme.text) } + requireViewById<ImageView>(R.id.tooltip_icon).apply { + val wrappedDrawable = DrawableCompat.wrap(this.drawable) + DrawableCompat.setTint(wrappedDrawable, tooltipColorScheme.icon) + } + } + + private fun tooltipViewGlobalCoordinates( + tooltipViewGlobalCoordinates: Point, + arrowDirection: TooltipArrowDirection, + tooltipDimen: Size, + ): Point { + var tooltipX = tooltipViewGlobalCoordinates.x + var tooltipY = tooltipViewGlobalCoordinates.y + + // Current values of [tooltipX]/[tooltipY] are the coordinates of tip of the arrow. + // Parameter x and y passed to [AdditionalSystemViewContainer] is the top left position of + // the window to be created. Hence we will need to move the coordinates left/up in order + // to position the tooltip correctly. + if (arrowDirection == TooltipArrowDirection.UP) { + // Arrow is placed at horizontal center on top edge of the tooltip. Hence decrement + // half of tooltip width from [tooltipX] to horizontally position the tooltip. + tooltipX -= tooltipDimen.width / 2 + } else { + // Arrow is placed at vertical center on the left edge of the tooltip. Hence decrement + // half of tooltip height from [tooltipY] to vertically position the tooltip. + tooltipY -= tooltipDimen.height / 2 + } + return Point(tooltipX, tooltipY) + } + + private fun tooltipDimens(tooltipView: View, arrowDirection: TooltipArrowDirection): Size { + val tooltipBackground = tooltipView.requireViewById<LinearLayout>(R.id.tooltip_container) + val arrowView = tooltipView.requireViewById<ImageView>(R.id.arrow_icon) + tooltipBackground.measure( + /* widthMeasureSpec= */ UNSPECIFIED, /* heightMeasureSpec= */ UNSPECIFIED) + arrowView.measure(/* widthMeasureSpec= */ UNSPECIFIED, /* heightMeasureSpec= */ UNSPECIFIED) + + var desiredWidth = + tooltipBackground.measuredWidth + + 2 * loadDimensionPixelSize(R.dimen.desktop_windowing_education_tooltip_padding) + var desiredHeight = + tooltipBackground.measuredHeight + + 2 * loadDimensionPixelSize(R.dimen.desktop_windowing_education_tooltip_padding) + if (arrowDirection == TooltipArrowDirection.UP) { + // desiredHeight currently does not account for the height of arrow, hence adding it. + desiredHeight += arrowView.height + } else { + // desiredWidth currently does not account for the width of arrow, hence adding it. + desiredWidth += arrowView.width + } + + return Size(desiredWidth, desiredHeight) + } + + private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int { + if (resourceId == Resources.ID_NULL) return 0 + return context.resources.getDimensionPixelSize(resourceId) + } + + /** + * The configuration for education view features: + * + * @property tooltipViewLayout Layout resource ID of the view to be used for education tooltip. + * @property tooltipViewGlobalCoordinates Global (screen) coordinates of the tip of the tooltip + * arrow. + * @property tooltipText Text to be added to the TextView of tooltip. + * @property arrowDirection Direction of arrow of the tooltip. + * @property onEducationClickAction Lambda to be executed when the tooltip is clicked. + * @property onDismissAction Lambda to be executed when the tooltip is dismissed. + */ + data class EducationViewConfig( + @LayoutRes val tooltipViewLayout: Int, + val tooltipColorScheme: TooltipColorScheme, + val tooltipViewGlobalCoordinates: Point, + val tooltipText: String, + val arrowDirection: TooltipArrowDirection, + val onEducationClickAction: () -> Unit, + val onDismissAction: () -> Unit, + ) + + /** + * Color scheme of education view: + * + * @property container Color of the container of the tooltip. + * @property text Text color of the [TextView] of education tooltip. + * @property icon Color to be filled in tooltip's icon. + */ + data class TooltipColorScheme( + @ColorInt val container: Int, + @ColorInt val text: Int, + @ColorInt val icon: Int, + ) + + /** Direction of arrow of the tooltip */ + enum class TooltipArrowDirection { + UP, + LEFT, + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt index 6f8e00143848..052cfaf2ccec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt @@ -20,6 +20,7 @@ import android.app.TaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_PINNED +import android.view.WindowInsets import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND @@ -46,3 +47,10 @@ val TaskInfo.isPinned: Boolean /** Whether the task is in multi-window windowing mode. */ val TaskInfo.isMultiWindow: Boolean get() = windowingMode == WINDOWING_MODE_MULTI_WINDOW + +/** Whether the task is requesting immersive mode. */ +val TaskInfo.requestingImmersive: Boolean + get() { + // Considered to be requesting immersive when requesting to hide the status bar. + return (requestedVisibleTypes and WindowInsets.Type.statusBars()) == 0 + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 8c102ebfb590..b5700ffb046b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -42,6 +42,7 @@ import com.android.wm.shell.R import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.windowdecor.WindowManagerWrapper import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer +import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data /** * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split). @@ -53,11 +54,20 @@ internal class AppHandleViewHolder( onCaptionButtonClickListener: OnClickListener, private val windowManagerWrapper: WindowManagerWrapper, private val handler: Handler -) : WindowDecorationViewHolder(rootView) { +) : WindowDecorationViewHolder<AppHandleViewHolder.HandleData>(rootView) { companion object { private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100 } + + data class HandleData( + val taskInfo: RunningTaskInfo, + val position: Point, + val width: Int, + val height: Int, + val isCaptionVisible: Boolean + ) : Data() + private lateinit var taskInfo: RunningTaskInfo private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle) @@ -89,7 +99,11 @@ internal class AppHandleViewHolder( } } - override fun bindData( + override fun bindData(data: HandleData) { + bindData(data.taskInfo, data.position, data.width, data.height, data.isCaptionVisible) + } + + private fun bindData( taskInfo: RunningTaskInfo, position: Point, width: Int, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index e9961655d979..cf03b3f74dc7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -16,12 +16,12 @@ package com.android.wm.shell.windowdecor.viewholder import android.annotation.ColorInt +import android.annotation.DrawableRes import android.app.ActivityManager.RunningTaskInfo import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color -import android.graphics.Point import android.graphics.Rect import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable @@ -46,9 +46,10 @@ import com.android.internal.R.attr.materialColorSecondaryContainer import com.android.internal.R.attr.materialColorSurfaceContainerHigh import com.android.internal.R.attr.materialColorSurfaceContainerLow import com.android.internal.R.attr.materialColorSurfaceDim +import com.android.window.flags.Flags import com.android.window.flags.Flags.enableMinimizeButton import com.android.wm.shell.R -import android.window.flags.DesktopModeFlags +import android.window.DesktopModeFlags import com.android.wm.shell.windowdecor.MaximizeButtonView import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.OPACITY_100 @@ -74,7 +75,14 @@ class AppHeaderViewHolder( appName: CharSequence, appIconBitmap: Bitmap, onMaximizeHoverAnimationFinishedListener: () -> Unit -) : WindowDecorationViewHolder(rootView) { +) : WindowDecorationViewHolder<AppHeaderViewHolder.HeaderData>(rootView) { + + data class HeaderData( + val taskInfo: RunningTaskInfo, + val isRequestingImmersive: Boolean, + val inFullImmersiveState: Boolean, + val hasGlobalFocus: Boolean + ) : Data() private val decorThemeUtil = DecorThemeUtil(context) private val lightColors = dynamicLightColorScheme(context) @@ -151,23 +159,28 @@ class AppHeaderViewHolder( onMaximizeHoverAnimationFinishedListener } - override fun bindData( + override fun bindData(data: HeaderData) { + bindData(data.taskInfo, data.isRequestingImmersive, data.inFullImmersiveState, + data.hasGlobalFocus) + } + + private fun bindData( taskInfo: RunningTaskInfo, - position: Point, - width: Int, - height: Int, - isCaptionVisible: Boolean + isRequestingImmersive: Boolean, + inFullImmersiveState: Boolean, + hasGlobalFocus: Boolean ) { if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) { - bindDataWithThemedHeaders(taskInfo) + bindDataWithThemedHeaders(taskInfo, isRequestingImmersive, inFullImmersiveState, + hasGlobalFocus) } else { - bindDataLegacy(taskInfo) + bindDataLegacy(taskInfo, hasGlobalFocus) } } - private fun bindDataLegacy(taskInfo: RunningTaskInfo) { - captionView.setBackgroundColor(getCaptionBackgroundColor(taskInfo)) - val color = getAppNameAndButtonColor(taskInfo) + private fun bindDataLegacy(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean) { + captionView.setBackgroundColor(getCaptionBackgroundColor(taskInfo, hasGlobalFocus)) + val color = getAppNameAndButtonColor(taskInfo, hasGlobalFocus) val alpha = Color.alpha(color) closeWindowButton.imageTintList = ColorStateList.valueOf(color) maximizeWindowButton.imageTintList = ColorStateList.valueOf(color) @@ -198,8 +211,13 @@ class AppHeaderViewHolder( minimizeWindowButton.isGone = !enableMinimizeButton() } - private fun bindDataWithThemedHeaders(taskInfo: RunningTaskInfo) { - val header = fillHeaderInfo(taskInfo) + private fun bindDataWithThemedHeaders( + taskInfo: RunningTaskInfo, + requestingImmersive: Boolean, + inFullImmersiveState: Boolean, + hasGlobalFocus: Boolean + ) { + val header = fillHeaderInfo(taskInfo, hasGlobalFocus) val headerStyle = getHeaderStyle(header) // Caption Background @@ -241,16 +259,19 @@ class AppHeaderViewHolder( } minimizeWindowButton.isGone = !enableMinimizeButton() // Maximize button. - maximizeButtonView.setAnimationTints( - darkMode = header.appTheme == Theme.DARK, - iconForegroundColor = colorStateList, - baseForegroundColor = foregroundColor, - rippleDrawable = createRippleDrawable( - color = foregroundColor, - cornerRadius = headerButtonsRippleRadius, - drawableInsets = maximizeDrawableInsets + maximizeButtonView.apply { + setAnimationTints( + darkMode = header.appTheme == Theme.DARK, + iconForegroundColor = colorStateList, + baseForegroundColor = foregroundColor, + rippleDrawable = createRippleDrawable( + color = foregroundColor, + cornerRadius = headerButtonsRippleRadius, + drawableInsets = maximizeDrawableInsets + ) ) - ) + setIcon(getMaximizeButtonIcon(requestingImmersive, inFullImmersiveState)) + } // Close button. closeWindowButton.apply { imageTintList = colorStateList @@ -320,6 +341,32 @@ class AppHeaderViewHolder( } } + @DrawableRes + private fun getMaximizeButtonIcon( + requestingImmersive: Boolean, + inFullImmersiveState: Boolean + ): Int = when { + shouldShowEnterFullImmersiveIcon(requestingImmersive, inFullImmersiveState) -> { + R.drawable.decor_desktop_mode_immersive_button_dark + } + shouldShowExitFullImmersiveIcon(requestingImmersive, inFullImmersiveState) -> { + R.drawable.decor_desktop_mode_immersive_exit_button_dark + } + else -> R.drawable.decor_desktop_mode_maximize_button_dark + } + + private fun shouldShowEnterFullImmersiveIcon( + requestingImmersive: Boolean, + inFullImmersiveState: Boolean + ): Boolean = Flags.enableFullyImmersiveInDesktop() + && requestingImmersive && !inFullImmersiveState + + private fun shouldShowExitFullImmersiveIcon( + requestingImmersive: Boolean, + inFullImmersiveState: Boolean + ): Boolean = Flags.enableFullyImmersiveInDesktop() + && requestingImmersive && inFullImmersiveState + private fun getHeaderStyle(header: Header): HeaderStyle { return HeaderStyle( background = getHeaderBackground(header), @@ -413,7 +460,7 @@ class AppHeaderViewHolder( } } - private fun fillHeaderInfo(taskInfo: RunningTaskInfo): Header { + private fun fillHeaderInfo(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean): Header { return Header( type = if (taskInfo.isTransparentCaptionBarAppearance) { Header.Type.CUSTOM @@ -421,7 +468,7 @@ class AppHeaderViewHolder( Header.Type.DEFAULT }, appTheme = decorThemeUtil.getAppTheme(taskInfo), - isFocused = taskInfo.isFocused, + isFocused = hasGlobalFocus, isAppearanceCaptionLight = taskInfo.isLightCaptionBarAppearance ) } @@ -502,19 +549,19 @@ class AppHeaderViewHolder( } @ColorInt - private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo): Int { + private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean): Int { if (taskInfo.isTransparentCaptionBarAppearance) { return Color.TRANSPARENT } val materialColorAttr: Int = if (isDarkMode()) { - if (!taskInfo.isFocused) { + if (!hasGlobalFocus) { materialColorSurfaceContainerHigh } else { materialColorSurfaceDim } } else { - if (!taskInfo.isFocused) { + if (!hasGlobalFocus) { materialColorSurfaceContainerLow } else { materialColorSecondaryContainer @@ -527,7 +574,7 @@ class AppHeaderViewHolder( } @ColorInt - private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo): Int { + private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean): Int { val materialColorAttr = when { taskInfo.isTransparentCaptionBarAppearance && taskInfo.isLightCaptionBarAppearance -> materialColorOnSecondaryContainer @@ -537,8 +584,8 @@ class AppHeaderViewHolder( else -> materialColorOnSecondaryContainer } val appDetailsOpacity = when { - isDarkMode() && !taskInfo.isFocused -> DARK_THEME_UNFOCUSED_OPACITY - !isDarkMode() && !taskInfo.isFocused -> LIGHT_THEME_UNFOCUSED_OPACITY + isDarkMode() && !hasGlobalFocus -> DARK_THEME_UNFOCUSED_OPACITY + !isDarkMode() && !hasGlobalFocus -> LIGHT_THEME_UNFOCUSED_OPACITY else -> FOCUSED_OPACITY } context.withStyledAttributes(null, intArrayOf(materialColorAttr), 0, 0) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt index 5ea55b367703..1fe743da966a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt @@ -17,31 +17,28 @@ package com.android.wm.shell.windowdecor.viewholder import android.app.ActivityManager.RunningTaskInfo import android.content.Context -import android.graphics.Point import android.view.View +import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data /** * Encapsulates the root [View] of a window decoration and its children to facilitate looking up * children (via findViewById) and updating to the latest data from [RunningTaskInfo]. */ -abstract class WindowDecorationViewHolder(rootView: View) { +abstract class WindowDecorationViewHolder<T : Data>(rootView: View) { val context: Context = rootView.context /** * A signal to the view holder that new data is available and that the views should be updated to * reflect it. */ - abstract fun bindData( - taskInfo: RunningTaskInfo, - position: Point, - width: Int, - height: Int, - isCaptionVisible: Boolean - ) + abstract fun bindData(data: T) /** Callback when the handle menu is opened. */ abstract fun onHandleMenuOpened() /** Callback when the handle menu is closed. */ abstract fun onHandleMenuClosed() + + /** Data clas that contains the information needed to update the view holder. */ + abstract class Data } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml index 40dbbac32c7f..c8df15d81345 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp index a231e381beda..176020ffc3b9 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp @@ -69,116 +69,6 @@ android_test { } //////////////////////////////////////////////////////////////////////////////// -// Begin cleanup after gcl merges - -filegroup { - name: "WMShellFlickerTestsSplitScreenGroup1-src", - srcs: [ - "src/**/A*.kt", - "src/**/B*.kt", - "src/**/C*.kt", - "src/**/D*.kt", - ], -} - -filegroup { - name: "WMShellFlickerTestsSplitScreenGroup2-src", - srcs: [ - "src/**/E*.kt", - ], -} - -filegroup { - name: "WMShellFlickerTestsSplitScreenGroup3-src", - srcs: [ - "src/**/S*.kt", - ], -} - -filegroup { - name: "WMShellFlickerTestsSplitScreenGroupOther-src", - srcs: [ - "src/**/*.kt", - ], -} - -android_test { - name: "WMShellFlickerTestsSplitScreenGroup1", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.splitscreen", - instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", - test_config_template: "AndroidTestTemplate.xml", - srcs: [ - ":WMShellFlickerTestsSplitScreenGroup1-src", - ], - static_libs: [ - "WMShellFlickerTestsBase", - "WMShellFlickerTestsSplitScreenBase", - ], - data: ["trace_config/*"], -} - -android_test { - name: "WMShellFlickerTestsSplitScreenGroup2", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.splitscreen", - instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", - test_config_template: "AndroidTestTemplate.xml", - srcs: [ - ":WMShellFlickerTestsSplitScreenGroup2-src", - ], - static_libs: [ - "WMShellFlickerTestsBase", - "WMShellFlickerTestsSplitScreenBase", - ], - data: ["trace_config/*"], -} - -android_test { - name: "WMShellFlickerTestsSplitScreenGroup3", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.splitscreen", - instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", - test_config_template: "AndroidTestTemplate.xml", - srcs: [ - ":WMShellFlickerTestsSplitScreenGroup3-src", - ], - static_libs: [ - "WMShellFlickerTestsBase", - "WMShellFlickerTestsSplitScreenBase", - ], - data: ["trace_config/*"], -} - -android_test { - name: "WMShellFlickerTestsSplitScreenGroupOther", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.splitscreen", - instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", - test_config_template: "AndroidTestTemplate.xml", - srcs: [ - ":WMShellFlickerTestsSplitScreenGroupOther-src", - ], - exclude_srcs: [ - ":WMShellFlickerTestsSplitScreenGroup1-src", - ":WMShellFlickerTestsSplitScreenGroup2-src", - ":WMShellFlickerTestsSplitScreenGroup3-src", - ], - static_libs: [ - "WMShellFlickerTestsBase", - "WMShellFlickerTestsSplitScreenBase", - ], - data: ["trace_config/*"], -} - -//////////////////////////////////////////////////////////////////////////////// -// End cleanup after gcl merges - -//////////////////////////////////////////////////////////////////////////////// // Begin breakdowns for FlickerTestsRotation module test_module_config { diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml index 85715db3d952..706c63244890 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml index 6c903a2e8c42..7df1675f541c 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml index 6c903a2e8c42..7df1675f541c 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 58559ac2c3ca..7b6cfe3f9f8a 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -30,7 +30,6 @@ filegroup { java_library { name: "wm-shell-flicker-utils", - platform_apis: true, optimize: { enabled: false, }, diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp index 29a9f1050b25..b016c9f94133 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp @@ -15,7 +15,7 @@ // package { - default_team: "trendy_team_app_compat", + default_team: "trendy_team_lse_app_compat", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" @@ -24,31 +24,6 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -//////////////////////////////////////////////////////////////////////////////// -// Begin cleanup after gcl merge - -filegroup { - name: "WMShellFlickerTestsAppCompat-src", - srcs: [ - "src/**/*.kt", - ], -} - -android_test { - name: "WMShellFlickerTestsOther", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker", - instrumentation_target_package: "com.android.wm.shell.flicker", - test_config_template: "AndroidTestTemplate.xml", - srcs: [":WMShellFlickerTestsAppCompat-src"], - static_libs: ["WMShellFlickerTestsBase"], - data: ["trace_config/*"], -} - -//////////////////////////////////////////////////////////////////////////////// -// End cleanup after gcl merge - android_test { name: "WMShellFlickerTestsAppCompat", defaults: ["WMShellFlickerTestsDefault"], diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml index f69a90cc793f..d87c1795cf7b 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt index 16c2d47f9db3..27303c1889c9 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test launching app in size compat mode. * - * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsAppCompat:OpenAppInSizeCompatModeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt index d85b7718aa56..e176f47d4094 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit -import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -32,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test launching app in size compat mode. * - * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest` + * To run this test: `atest WMShellFlickerTestsAppCompat:OpenTransparentActivityTest` * * Actions: * ``` @@ -109,9 +108,7 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return LegacyFlickerTestFactory.nonRotationTests( - supportedRotations = listOf(Rotation.ROTATION_90) - ) + return LegacyFlickerTestFactory.nonRotationTests() } } } diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt index 164534c14d28..9b8c949a1705 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -20,7 +20,6 @@ import android.graphics.Rect import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice import android.tools.NavBar -import android.tools.Rotation import android.tools.flicker.assertions.FlickerTest import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -36,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test quick switching to letterboxed app from launcher * - * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest` + * To run this test: `atest WMShellFlickerTestsAppCompat:QuickSwitchLauncherToLetterboxAppTest` * * Actions: * ``` @@ -266,8 +265,7 @@ class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAp @JvmStatic fun getParams(): Collection<FlickerTest> { return LegacyFlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), - supportedRotations = listOf(Rotation.ROTATION_90) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt index 034d54b185ed..77423af667f7 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized /** * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right. * - * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest` + * To run this test: `atest WMShellFlickerTestsAppCompat:RepositionFixedPortraitAppTest` * * Actions: * diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt index 443fac19c7e7..5459ef03e360 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test restarting app in size compat mode. * - * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest` + * To run this test: `atest WMShellFlickerTestsAppCompat:RestartAppInSizeCompatModeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt index 22543aa9f773..5bb96401bc86 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt @@ -45,7 +45,7 @@ import org.junit.runners.Parameterized /** * Test rotating an immersive app in fullscreen. * - * To run this test: `atest WMShellFlickerTestsOther:RotateImmersiveAppInFullscreenTest` + * To run this test: `atest WMShellFlickerTestsAppCompat:RotateImmersiveAppInFullscreenTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml index b76d06565700..99969e71238a 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp index 4165ed093929..ddbc681f7cac 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp @@ -29,92 +29,12 @@ filegroup { srcs: ["src/**/apps/*.kt"], } -//////////////////////////////////////////////////////////////////////////////// -// Begin cleanup after gcl merges - -filegroup { - name: "WMShellFlickerTestsPip1-src", - srcs: [ - "src/**/A*.kt", - "src/**/B*.kt", - "src/**/C*.kt", - "src/**/D*.kt", - "src/**/F*.kt", - "src/**/S*.kt", - ], -} - -filegroup { - name: "WMShellFlickerTestsPip2-src", - srcs: [ - "src/**/E*.kt", - ], -} - -filegroup { - name: "WMShellFlickerTestsPip3-src", - srcs: ["src/**/*.kt"], -} - filegroup { name: "WMShellFlickerTestsPipCommon-src", srcs: ["src/**/common/*.kt"], } android_test { - name: "WMShellFlickerTestsPip1", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.pip", - instrumentation_target_package: "com.android.wm.shell.flicker.pip", - test_config_template: "AndroidTestTemplate.xml", - srcs: [ - ":WMShellFlickerTestsPip1-src", - ":WMShellFlickerTestsPipCommon-src", - ], - static_libs: ["WMShellFlickerTestsBase"], - data: ["trace_config/*"], -} - -android_test { - name: "WMShellFlickerTestsPip2", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.pip", - instrumentation_target_package: "com.android.wm.shell.flicker.pip", - test_config_template: "AndroidTestTemplate.xml", - srcs: [ - ":WMShellFlickerTestsPip2-src", - ":WMShellFlickerTestsPipCommon-src", - ], - static_libs: ["WMShellFlickerTestsBase"], - data: ["trace_config/*"], -} - -android_test { - name: "WMShellFlickerTestsPip3", - defaults: ["WMShellFlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.wm.shell.flicker.pip", - instrumentation_target_package: "com.android.wm.shell.flicker.pip", - test_config_template: "AndroidTestTemplate.xml", - srcs: [ - ":WMShellFlickerTestsPip3-src", - ":WMShellFlickerTestsPipCommon-src", - ], - exclude_srcs: [ - ":WMShellFlickerTestsPip1-src", - ":WMShellFlickerTestsPip2-src", - ":WMShellFlickerTestsPipApps-src", - ], - static_libs: ["WMShellFlickerTestsBase"], - data: ["trace_config/*"], -} - -//////////////////////////////////////////////////////////////////////////////// -// End cleanup after gcl merges - -android_test { name: "WMShellFlickerTestsPip", defaults: ["WMShellFlickerTestsDefault"], manifest: "AndroidManifest.xml", @@ -122,6 +42,7 @@ android_test { instrumentation_target_package: "com.android.wm.shell.flicker.pip", test_config_template: "AndroidTestTemplate.xml", srcs: ["src/**/*.kt"], + exclude_srcs: [":WMShellFlickerTestsPipApps-src"], static_libs: ["WMShellFlickerTestsBase"], data: ["trace_config/*"], } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml index 041978c371ff..19c3e4048d69 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml index bf040d2a95f4..7505860709e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml @@ -24,6 +24,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="on"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="on"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index a9ed13a099f3..a248303b1c33 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -38,7 +38,7 @@ import kotlin.math.abs /** * Test entering pip from an app via auto-enter property when navigating to home. * - * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipOnGoToHomeTest` + * To run this test: `atest WMShellFlickerTestsPip:AutoEnterPipOnGoToHomeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt index d059211088aa..df952c925720 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt @@ -30,7 +30,7 @@ import org.junit.runners.Parameterized /** * Test auto entering pip using a source rect hint. * - * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipWithSourceRectHintTest` + * To run this test: `atest WMShellFlickerTestsPip:AutoEnterPipWithSourceRectHintTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index 3ffc9d7b87f6..302b8c414979 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized /** * Test closing a pip window by swiping it to the bottom-center of the screen * - * To run this test: `atest WMShellFlickerTestsPip1:ClosePipBySwipingDownTest` + * To run this test: `atest WMShellFlickerTestsPip:ClosePipBySwipingDownTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index d177624378c1..77a1edb7039a 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -30,7 +30,7 @@ import org.junit.runners.Parameterized /** * Test closing a pip window via the dismiss button * - * To run this test: `atest WMShellFlickerTestsPip1:ClosePipWithDismissButtonTest` + * To run this test: `atest WMShellFlickerTestsPip:ClosePipWithDismissButtonTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index a86803d058f8..6e32d6412b50 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from an app via [onUserLeaveHint] and by navigating to home. * - * To run this test: `atest WMShellFlickerTestsPip2:EnterPipOnUserLeaveHintTest` + * To run this test: `atest WMShellFlickerTestsPip:EnterPipOnUserLeaveHintTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index d0e8215e662e..9a6cb61cfc66 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -46,7 +46,7 @@ import org.junit.runners.Parameterized /** * Test entering pip while changing orientation (from app in landscape to pip window in portrait) * - * To run this test: `atest WMShellFlickerTestsPip2:EnterPipToOtherOrientation` + * To run this test: `atest WMShellFlickerTestsPip:EnterPipToOtherOrientation` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index d92f55af578f..6b4751cee3bb 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -28,7 +28,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from an app by interacting with the app UI * - * To run this test: `atest WMShellFlickerTestsPip2:EnterPipViaAppUiButtonTest` + * To run this test: `atest WMShellFlickerTestsPip:EnterPipViaAppUiButtonTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 8c0817d6e287..8d0bc0f5a155 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -28,7 +28,7 @@ import org.junit.runners.Parameterized /** * Test expanding a pip window back to full screen via the expand button * - * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaExpandButtonTest` + * To run this test: `atest WMShellFlickerTestsPip:ExitPipToAppViaExpandButtonTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index 90a9623056ce..939f3280d2e6 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -28,7 +28,7 @@ import org.junit.runners.Parameterized /** * Test expanding a pip window back to full screen via an intent * - * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaIntentTest` + * To run this test: `atest WMShellFlickerTestsPip:ExitPipToAppViaIntentTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 9306c77a1c43..258663b5556d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -33,7 +33,7 @@ import org.junit.runners.Parameterized /** * Test expanding a pip window by double-clicking it * - * To run this test: `atest WMShellFlickerTestsPip2:ExpandPipOnDoubleClickTest` + * To run this test: `atest WMShellFlickerTestsPip:ExpandPipOnDoubleClickTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt index cb8ee27f29e2..5f8ac2af241b 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from an app via auto-enter property when navigating to home from split screen. * - * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenAutoEnterPipOnGoToHomeTest` + * To run this test: `atest WMShellFlickerTestsPip:FromSplitScreenAutoEnterPipOnGoToHomeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt index d03d7799d675..48c85a84e556 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from an app via auto-enter property when navigating to home from split screen. * - * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenEnterPipOnUserLeaveHintTest` + * To run this test: `atest WMShellFlickerTestsPip:FromSplitScreenEnterPipOnUserLeaveHintTest` * * Actions: * ``` @@ -183,12 +183,6 @@ class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : } /** {@inheritDoc} */ - @FlakyTest(bugId = 312446524) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - /** {@inheritDoc} */ @Test @FlakyTest(bugId = 336510055) override fun entireScreenCovered() { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index 265eb4416a2b..ee62cf59b2f9 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized /** * Test Pip movement with Launcher shelf height change (increase). * - * To run this test: `atest WMShellFlickerTestsPip3:MovePipDownOnShelfHeightChange` + * To run this test: `atest WMShellFlickerTestsPip:MovePipDownOnShelfHeightChange` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt index 8d6be64da21d..4d643f7b4408 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized /** * Test Pip movement with Launcher shelf height change (decrease). * - * To run this test: `atest WMShellFlickerTestsPip3:MovePipUpOnShelfHeightChangeTest` + * To run this test: `atest WMShellFlickerTestsPip:MovePipUpOnShelfHeightChangeTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt index 9109eafacf63..c6cf3411835c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test Pip Stack in bounds after rotations. * - * To run this test: `atest WMShellFlickerTestsPip1:ShowPipAndRotateDisplay` + * To run this test: `atest WMShellFlickerTestsPip:ShowPipAndRotateDisplay` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt index 1fc9d9910a15..7b04b766a191 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from Maps app by interacting with the app UI * - * To run this test: `atest WMShellFlickerTests:MapsEnterPipTest` + * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:MapsEnterPipTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt index 68fa7c7af740..691194609343 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from Netflix app by interacting with the app UI * - * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest` + * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:NetflixEnterPipTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt index 7873a85d515d..5e54f30dae8a 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from YouTube app by interacting with the app UI * - * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest` + * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:YouTubeEnterPipTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt index 72be3d85ec8b..159cba4a559e 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized /** * Test entering pip from YouTube app by interacting with the app UI * - * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest` + * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:YouTubeEnterPipTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt index 8a073abf032c..6a84b2803d8c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipBasicTest` */ +/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTestsPip:TvPipBasicTest` */ @RequiresDevice @RunWith(Parameterized::class) class TvPipBasicTest(private val radioButtonId: String, private val pipWindowRatio: Rational?) : diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt index d4cd6da4acb1..09e8745a0224 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt @@ -27,7 +27,7 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipMenuTests` */ +/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTestsPip:TvPipMenuTests` */ @RequiresDevice class TvPipMenuTests : TvPipTestBase() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 227060d15640..dee0b23a42f5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -860,15 +860,9 @@ public class BackAnimationControllerTest extends ShellTestCase { } private void doMotionEvent(int actionDown, int coordinate) { - doMotionEvent(actionDown, coordinate, 0); - } - - private void doMotionEvent(int actionDown, int coordinate, float velocity) { mController.onMotionEvent( /* touchX */ coordinate, /* touchY */ coordinate, - /* velocityX = */ velocity, - /* velocityY = */ velocity, /* keyAction */ actionDown, /* swipeEdge */ BackEvent.EDGE_LEFT); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 9b019ddb8362..1da4ef6b5a8b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -54,8 +54,6 @@ public class BackProgressAnimatorTest { /* touchX = */ touchX, /* touchY = */ 0, /* progress = */ progress, - /* velocityX = */ 0, - /* velocityY = */ 0, /* triggerBack = */ false, /* swipeEdge = */ BackEvent.EDGE_LEFT, /* departingAnimationTarget = */ null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt index 5b5ef6f48789..2235c20d7f21 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt @@ -222,8 +222,6 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() { /* touchX = */ touchX, /* touchY = */ 0f, /* progress = */ progress, - /* velocityX = */ 0f, - /* velocityY = */ 0f, /* triggerBack = */ false, /* swipeEdge = */ BackEvent.EDGE_LEFT, /* departingAnimationTarget = */ null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index f8f0db930e6c..0373bbd43043 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -26,6 +26,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -34,6 +36,12 @@ import static org.mockito.Mockito.verifyZeroInteractions; import android.graphics.Insets; import android.graphics.Point; +import android.os.Looper; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.IWindowManager; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -47,6 +55,7 @@ import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -61,11 +70,16 @@ import java.util.concurrent.Executor; */ @SmallTest public class DisplayImeControllerTest extends ShellTestCase { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Mock private SurfaceControl.Transaction mT; @Mock private ShellInit mShellInit; + @Mock + private IWindowManager mWm; + private DisplayImeController mDisplayImeController; private DisplayImeController.PerDisplay mPerDisplay; private Executor mExecutor; @@ -73,7 +87,8 @@ public class DisplayImeControllerTest extends ShellTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mExecutor = spy(Runnable::run); - mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() { + mDisplayImeController = new DisplayImeController(mWm, mShellInit, null, null, + new TransactionPool() { @Override public SurfaceControl.Transaction acquire() { return mT; @@ -84,8 +99,10 @@ public class DisplayImeControllerTest extends ShellTestCase { } }, mExecutor) { @Override - void removeImeSurface(int displayId) { } - }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0); + void removeImeSurface(int displayId) { + } + }; + mPerDisplay = mDisplayImeController.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0); } @Test @@ -95,12 +112,14 @@ public class DisplayImeControllerTest extends ShellTestCase { @Test public void insetsControlChanged_schedulesNoWorkOnExecutor() { + Looper.prepare(); mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl()); verifyZeroInteractions(mExecutor); } @Test public void insetsChanged_schedulesNoWorkOnExecutor() { + Looper.prepare(); mPerDisplay.insetsChanged(insetsStateWithIme(false)); verifyZeroInteractions(mExecutor); } @@ -117,7 +136,10 @@ public class DisplayImeControllerTest extends ShellTestCase { verifyZeroInteractions(mExecutor); } + // With the refactor, the control's isInitiallyVisible is used to apply to the IME, therefore + // this test is obsolete @Test + @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER) public void reappliesVisibilityToChangedLeash() { verifyZeroInteractions(mT); mPerDisplay.mImeShowing = false; @@ -136,6 +158,7 @@ public class DisplayImeControllerTest extends ShellTestCase { @Test public void insetsControlChanged_updateImeSourceControl() { + Looper.prepare(); mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl()); assertNotNull(mPerDisplay.mImeSourceControl); @@ -143,6 +166,19 @@ public class DisplayImeControllerTest extends ShellTestCase { assertNull(mPerDisplay.mImeSourceControl); } + @Test + @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER) + public void setImeInputTargetRequestedVisibility_invokeOnImeRequested() { + var mockPp = mock(DisplayImeController.ImePositionProcessor.class); + mDisplayImeController.addPositionProcessor(mockPp); + + mPerDisplay.setImeInputTargetRequestedVisibility(true); + verify(mockPp).onImeRequested(anyInt(), eq(true)); + + mPerDisplay.setImeInputTargetRequestedVisibility(false); + verify(mockPp).onImeRequested(anyInt(), eq(false)); + } + private InsetsSourceControl[] insetsSourceControl() { return new InsetsSourceControl[]{ new InsetsSourceControl( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 177e47a342f6..c52d9dd24165 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -19,7 +19,7 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static com.google.common.truth.Truth.assertThat; @@ -136,7 +136,7 @@ public class SplitLayoutTests extends ShellTestCase { @Test public void testSetDivideRatio() { mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */); - mSplitLayout.setDivideRatio(SNAP_TO_50_50); + mSplitLayout.setDivideRatio(SNAP_TO_2_50_50); assertThat(mSplitLayout.getDividerPosition()).isEqualTo( mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt new file mode 100644 index 000000000000..9b4cc17e19d9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WindowingMode +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl +import android.view.WindowManager +import android.window.TransitionInfo +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.common.ShellExecutor +import java.util.function.Supplier +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.kotlin.mock + +/** + * Test class for [CloseDesktopTaskTransitionHandler] + * + * Usage: atest WMShellUnitTests:CloseDesktopTaskTransitionHandlerTest + */ +@SmallTest +@RunWithLooper +@RunWith(AndroidTestingRunner::class) +class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { + + @Mock lateinit var testExecutor: ShellExecutor + @Mock lateinit var closingTaskLeash: SurfaceControl + + private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } + + private lateinit var handler: CloseDesktopTaskTransitionHandler + + @Before + fun setUp() { + handler = + CloseDesktopTaskTransitionHandler( + context, + testExecutor, + testExecutor, + transactionSupplier + ) + } + + @Test + fun handleRequest_returnsNull() { + assertNull(handler.handleRequest(mock(), mock())) + } + + @Test + fun startAnimation_openTransition_returnsFalse() { + val animates = + handler.startAnimation( + transition = mock(), + info = + createTransitionInfo( + type = WindowManager.TRANSIT_OPEN, + task = createTask(WINDOWING_MODE_FREEFORM) + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertFalse("Should not animate open transition", animates) + } + + @Test + fun startAnimation_closeTransitionFullscreenTask_returnsFalse() { + val animates = + handler.startAnimation( + transition = mock(), + info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertFalse("Should not animate fullscreen task close transition", animates) + } + + @Test + fun startAnimation_closeTransitionOpeningFreeformTask_returnsFalse() { + val animates = + handler.startAnimation( + transition = mock(), + info = + createTransitionInfo( + changeMode = WindowManager.TRANSIT_OPEN, + task = createTask(WINDOWING_MODE_FREEFORM) + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertFalse("Should not animate opening freeform task close transition", animates) + } + + @Test + fun startAnimation_closeTransitionClosingFreeformTask_returnsTrue() { + val animates = + handler.startAnimation( + transition = mock(), + info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertTrue("Should animate closing freeform task close transition", animates) + } + + private fun createTransitionInfo( + type: Int = WindowManager.TRANSIT_CLOSE, + changeMode: Int = WindowManager.TRANSIT_CLOSE, + task: RunningTaskInfo + ): TransitionInfo = + TransitionInfo(type, 0 /* flags */).apply { + addChange( + TransitionInfo.Change(mock(), closingTaskLeash).apply { + mode = changeMode + parent = null + taskInfo = task + } + ) + } + + private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(windowingMode) + .build() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt index 628c9cdd9339..3e9c732b9c3b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt @@ -98,7 +98,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { private lateinit var mockitoSession: StaticMockitoSession private lateinit var handler: DesktopActivityOrientationChangeHandler private lateinit var shellInit: ShellInit - private lateinit var taskRepository: DesktopModeTaskRepository + private lateinit var taskRepository: DesktopRepository private lateinit var testScope: CoroutineScope // Mock running tasks are registered here so we can get the list from mock shell task organizer. private val runningTasks = mutableListOf<RunningTaskInfo>() @@ -116,7 +116,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) taskRepository = - DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope) + DesktopRepository(context, shellInit, persistentRepository, testScope) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt new file mode 100644 index 000000000000..2e9effb44d67 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS +import android.os.Binder +import android.os.IBinder +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE +import android.view.WindowManager.TransitionFlags +import android.view.WindowManager.TransitionType +import android.window.TransitionInfo +import android.window.WindowContainerToken +import android.window.WindowContainerTransaction +import androidx.test.filters.SmallTest +import com.android.window.flags.Flags +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.Transitions +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** + * Tests for [DesktopFullImmersiveTransitionHandler]. + * + * Usage: atest WMShellUnitTests:DesktopFullImmersiveTransitionHandlerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { + + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + @Mock private lateinit var mockTransitions: Transitions + private lateinit var desktopRepository: DesktopRepository + @Mock private lateinit var mockDisplayController: DisplayController + @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer + private val transactionSupplier = { SurfaceControl.Transaction() } + + private lateinit var immersiveHandler: DesktopFullImmersiveTransitionHandler + + @Before + fun setUp() { + desktopRepository = DesktopRepository( + context, ShellInit(TestShellExecutor()), mock(), mock() + ) + whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY)) + .thenReturn(DisplayLayout()) + immersiveHandler = DesktopFullImmersiveTransitionHandler( + transitions = mockTransitions, + desktopRepository = desktopRepository, + displayController = mockDisplayController, + shellTaskOrganizer = mockShellTaskOrganizer, + transactionSupplier = transactionSupplier, + ) + } + + @Test + fun enterImmersive_transitionReady_updatesRepository() { + val task = createFreeformTask() + val mockBinder = mock(IBinder::class.java) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + .thenReturn(mockBinder) + desktopRepository.setTaskInFullImmersiveState( + displayId = task.displayId, + taskId = task.taskId, + immersive = false + ) + + immersiveHandler.moveTaskToImmersive(task) + immersiveHandler.onTransitionReady(mockBinder, createTransitionInfo()) + + assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isTrue() + } + + @Test + fun exitImmersive_transitionReady_updatesRepository() { + val task = createFreeformTask() + val mockBinder = mock(IBinder::class.java) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + .thenReturn(mockBinder) + desktopRepository.setTaskInFullImmersiveState( + displayId = task.displayId, + taskId = task.taskId, + immersive = true + ) + + immersiveHandler.moveTaskToNonImmersive(task) + immersiveHandler.onTransitionReady(mockBinder, createTransitionInfo()) + + assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse() + } + + @Test + fun enterImmersive_inProgress_ignores() { + val task = createFreeformTask() + val mockBinder = mock(IBinder::class.java) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + .thenReturn(mockBinder) + + immersiveHandler.moveTaskToImmersive(task) + immersiveHandler.moveTaskToImmersive(task) + + verify(mockTransitions, times(1)) + .startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)) + } + + @Test + fun exitImmersive_inProgress_ignores() { + val task = createFreeformTask() + val mockBinder = mock(IBinder::class.java) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + .thenReturn(mockBinder) + + immersiveHandler.moveTaskToNonImmersive(task) + immersiveHandler.moveTaskToNonImmersive(task) + + verify(mockTransitions, times(1)) + .startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_inImmersive_addsPendingExit() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + + immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + exit.transition == transition && exit.displayId == DEFAULT_DISPLAY + && exit.taskId == task.taskId + }).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_notInImmersive_doesNotAddPendingExit() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = false + ) + + immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + exit.transition == transition && exit.displayId == DEFAULT_DISPLAY + && exit.taskId == task.taskId + }).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_byDisplay_inImmersive_changesTaskBounds() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + + immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + assertThat(wct.hasBoundsChange(task.token)).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_byDisplay_notInImmersive_doesNotChangeTaskBounds() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = false + ) + + immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + assertThat(wct.hasBoundsChange(task.token)).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_byTask_inImmersive_changesTaskBounds() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + + immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + + assertThat(wct.hasBoundsChange(task.token)).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_byTask_notInImmersive_doesNotChangeTaskBounds() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = false + ) + + immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId) + + assertThat(wct.hasBoundsChange(task.token)).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_byTask_inImmersive_addsPendingExitOnRun() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + + immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition) + + assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + exit.transition == transition && exit.displayId == DEFAULT_DISPLAY + && exit.taskId == task.taskId + }).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_byTask_notInImmersive_doesNotAddPendingExitOnRun() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = false + ) + + immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition) + + assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + exit.transition == transition && exit.displayId == DEFAULT_DISPLAY + && exit.taskId == task.taskId + }).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTransitionReady_pendingExit_removesPendingExit() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + immersiveHandler.onTransitionReady( + transition = transition, + info = createTransitionInfo( + changes = listOf( + TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } + ) + ) + ) + + assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + exit.transition == transition && exit.displayId == DEFAULT_DISPLAY + && exit.taskId == task.taskId + }).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTransitionReady_pendingExit_updatesRepository() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + immersiveHandler.onTransitionReady( + transition = transition, + info = createTransitionInfo( + changes = listOf( + TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } + ) + ) + ) + + assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse() + } + + private fun createTransitionInfo( + @TransitionType type: Int = TRANSIT_CHANGE, + @TransitionFlags flags: Int = 0, + changes: List<TransitionInfo.Change> = emptyList() + ): TransitionInfo = TransitionInfo(type, flags).apply { + changes.forEach { change -> addChange(change) } + } + + private fun WindowContainerTransaction.hasBoundsChange(token: WindowContainerToken): Boolean = + this.changes.any { change -> + change.key == token.asBinder() + && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0 + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt new file mode 100644 index 000000000000..07de0716200c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2024 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.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WindowingMode +import android.os.Handler +import android.os.IBinder +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl +import android.view.WindowManager +import android.window.TransitionInfo +import android.window.WindowContainerTransaction +import androidx.test.filters.SmallTest +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE +import com.android.internal.jank.InteractionJankMonitor +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.freeform.FreeformTaskTransitionHandler +import com.android.wm.shell.transition.Transitions +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +/** + * Test class for [DesktopMixedTransitionHandler] + * + * Usage: atest WMShellUnitTests:DesktopMixedTransitionHandlerTest + */ +@SmallTest +@RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DesktopMixedTransitionHandlerTest : ShellTestCase() { + + @Mock lateinit var transitions: Transitions + @Mock lateinit var desktopRepository: DesktopRepository + @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler + @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var mockHandler: Handler + @Mock lateinit var closingTaskLeash: SurfaceControl + + private lateinit var mixedHandler: DesktopMixedTransitionHandler + + @Before + fun setUp() { + mixedHandler = + DesktopMixedTransitionHandler( + context, + transitions, + desktopRepository, + freeformTaskTransitionHandler, + closeDesktopTaskTransitionHandler, + interactionJankMonitor, + mockHandler + ) + } + + @Test + fun startWindowingModeTransition_callsFreeformTaskTransitionHandler() { + val windowingMode = WINDOWING_MODE_FULLSCREEN + val wct = WindowContainerTransaction() + + mixedHandler.startWindowingModeTransition(windowingMode, wct) + + verify(freeformTaskTransitionHandler).startWindowingModeTransition(windowingMode, wct) + } + + @Test + fun startMinimizedModeTransition_callsFreeformTaskTransitionHandler() { + val wct = WindowContainerTransaction() + whenever(freeformTaskTransitionHandler.startMinimizedModeTransition(any())) + .thenReturn(mock()) + + mixedHandler.startMinimizedModeTransition(wct) + + verify(freeformTaskTransitionHandler).startMinimizedModeTransition(wct) + } + + @Test + fun startRemoveTransition_startsCloseTransition() { + val wct = WindowContainerTransaction() + + mixedHandler.startRemoveTransition(wct) + + verify(transitions).startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler) + } + + @Test + fun handleRequest_returnsNull() { + assertNull(mixedHandler.handleRequest(mock(), mock())) + } + + @Test + fun startAnimation_withoutClosingDesktopTask_returnsFalse() { + val transition = mock<IBinder>() + val transitionInfo = + createTransitionInfo( + changeMode = WindowManager.TRANSIT_OPEN, + task = createTask(WINDOWING_MODE_FREEFORM) + ) + whenever(freeformTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())) + .thenReturn(true) + + val started = mixedHandler.startAnimation( + transition = transition, + info = transitionInfo, + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertFalse("Should not start animation without closing desktop task", started) + } + + @Test + fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() { + val transition = mock<IBinder>() + val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)) + whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(2) + whenever( + closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any()) + ) + .thenReturn(true) + + val started = mixedHandler.startAnimation( + transition = transition, + info = transitionInfo, + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + assertTrue("Should delegate animation to close transition handler", started) + verify(closeDesktopTaskTransitionHandler) + .startAnimation(eq(transition), eq(transitionInfo), any(), any(), any()) + } + + @Test + fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() { + val transition = mock<IBinder>() + val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)) + whenever(desktopRepository.getActiveNonMinimizedTaskCount(any())).thenReturn(1) + whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any())) + .thenReturn(mock()) + + mixedHandler.startAnimation( + transition = transition, + info = transitionInfo, + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + verify(transitions) + .dispatchTransition( + eq(transition), + eq(transitionInfo), + any(), + any(), + any(), + eq(mixedHandler) + ) + verify(interactionJankMonitor) + .begin( + closingTaskLeash, + context, + mockHandler, + CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE + ) + } + + private fun createTransitionInfo( + type: Int = WindowManager.TRANSIT_CLOSE, + changeMode: Int = WindowManager.TRANSIT_CLOSE, + task: RunningTaskInfo + ): TransitionInfo = + TransitionInfo(type, 0 /* flags */).apply { + addChange( + TransitionInfo.Change(mock(), closingTaskLeash).apply { + mode = changeMode + parent = null + taskInfo = task + } + ) + } + + private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(windowingMode) + .build() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index ca972296e8d4..d7a132dfa1be 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -16,13 +16,22 @@ package com.android.wm.shell.desktopmode +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.window.flags.Flags +import com.android.wm.shell.EventLogTags +import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON @@ -34,14 +43,19 @@ import org.mockito.kotlin.eq /** * Tests for [DesktopModeEventLogger]. */ -class DesktopModeEventLoggerTest { +class DesktopModeEventLoggerTest : ShellTestCase() { private val desktopModeEventLogger = DesktopModeEventLogger() @JvmField - @Rule + @Rule(order = 0) val extendedMockitoRule = ExtendedMockitoRule.Builder(this) - .mockStatic(FrameworkStatsLog::class.java).build()!! + .mockStatic(FrameworkStatsLog::class.java) + .mockStatic(EventLogTags::class.java).build()!! + + @JvmField + @Rule(order = 1) + val setFlagsRule = SetFlagsRule() @Test fun logSessionEnter_enterReason() = runBlocking { @@ -60,6 +74,11 @@ class DesktopModeEventLoggerTest { eq(SESSION_ID) ) } + verify { + EventLogTags.writeWmShellEnterDesktopMode( + eq(EnterReason.UNKNOWN_ENTER.reason), + eq(SESSION_ID)) + } } @Test @@ -79,6 +98,11 @@ class DesktopModeEventLoggerTest { eq(SESSION_ID) ) } + verify { + EventLogTags.writeWmShellExitDesktopMode( + eq(ExitReason.UNKNOWN_EXIT.reason), + eq(SESSION_ID)) + } } @Test @@ -108,6 +132,22 @@ class DesktopModeEventLoggerTest { /* visible_task_count */ eq(TASK_COUNT)) } + + verify { + EventLogTags.writeWmShellDesktopModeTaskUpdate( + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), + eq(TASK_UPDATE.instanceId), + eq(TASK_UPDATE.uid), + eq(TASK_UPDATE.taskHeight), + eq(TASK_UPDATE.taskWidth), + eq(TASK_UPDATE.taskX), + eq(TASK_UPDATE.taskY), + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON), + eq(TASK_COUNT)) + } } @Test @@ -137,6 +177,22 @@ class DesktopModeEventLoggerTest { /* visible_task_count */ eq(TASK_COUNT)) } + + verify { + EventLogTags.writeWmShellDesktopModeTaskUpdate( + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), + eq(TASK_UPDATE.instanceId), + eq(TASK_UPDATE.uid), + eq(TASK_UPDATE.taskHeight), + eq(TASK_UPDATE.taskWidth), + eq(TASK_UPDATE.taskX), + eq(TASK_UPDATE.taskY), + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON), + eq(TASK_COUNT)) + } } @Test @@ -167,6 +223,22 @@ class DesktopModeEventLoggerTest { /* visible_task_count */ eq(TASK_COUNT)) } + + verify { + EventLogTags.writeWmShellDesktopModeTaskUpdate( + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq(TASK_UPDATE.instanceId), + eq(TASK_UPDATE.uid), + eq(TASK_UPDATE.taskHeight), + eq(TASK_UPDATE.taskWidth), + eq(TASK_UPDATE.taskX), + eq(TASK_UPDATE.taskY), + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON), + eq(TASK_COUNT)) + } } @Test @@ -200,6 +272,22 @@ class DesktopModeEventLoggerTest { /* visible_task_count */ eq(TASK_COUNT)) } + + verify { + EventLogTags.writeWmShellDesktopModeTaskUpdate( + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq(TASK_UPDATE.instanceId), + eq(TASK_UPDATE.uid), + eq(TASK_UPDATE.taskHeight), + eq(TASK_UPDATE.taskWidth), + eq(TASK_UPDATE.taskX), + eq(TASK_UPDATE.taskY), + eq(SESSION_ID), + eq(MinimizeReason.TASK_LIMIT.reason), + eq(UNSET_UNMINIMIZE_REASON), + eq(TASK_COUNT)) + } } @Test @@ -233,9 +321,83 @@ class DesktopModeEventLoggerTest { /* visible_task_count */ eq(TASK_COUNT)) } + + verify { + EventLogTags.writeWmShellDesktopModeTaskUpdate( + eq(FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq(TASK_UPDATE.instanceId), + eq(TASK_UPDATE.uid), + eq(TASK_UPDATE.taskHeight), + eq(TASK_UPDATE.taskWidth), + eq(TASK_UPDATE.taskX), + eq(TASK_UPDATE.taskY), + eq(SESSION_ID), + eq(UNSET_MINIMIZE_REASON), + eq(UnminimizeReason.TASKBAR_TAP.reason), + eq(TASK_COUNT)) + } } - companion object { + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS) + fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() = runBlocking { + desktopModeEventLogger.logTaskResizingStarted(sessionId = SESSION_ID, createTaskSizeUpdate()) + + verify { + FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), + /* resize_trigger */ + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER), + /* resizing_stage */ + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE), + /* input_method */ + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), + /* desktop_mode_session_id */ + eq(SESSION_ID), + /* instance_id */ + eq(TASK_SIZE_UPDATE.instanceId), + /* uid */ + eq(TASK_SIZE_UPDATE.uid), + /* task_height */ + eq(TASK_SIZE_UPDATE.taskHeight), + /* task_width */ + eq(TASK_SIZE_UPDATE.taskWidth), + /* display_area */ + eq(TASK_SIZE_UPDATE.displayArea), + ) + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS) + fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() = runBlocking { + desktopModeEventLogger.logTaskResizingEnded(sessionId = SESSION_ID, createTaskSizeUpdate()) + + verify { + FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), + /* resize_trigger */ + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER), + /* resizing_stage */ + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE), + /* input_method */ + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), + /* desktop_mode_session_id */ + eq(SESSION_ID), + /* instance_id */ + eq(TASK_SIZE_UPDATE.instanceId), + /* uid */ + eq(TASK_SIZE_UPDATE.uid), + /* task_height */ + eq(TASK_SIZE_UPDATE.taskHeight), + /* task_width */ + eq(TASK_SIZE_UPDATE.taskWidth), + /* display_area */ + eq(TASK_SIZE_UPDATE.displayArea), + ) + } + } + + private companion object { private const val SESSION_ID = 1 private const val TASK_ID = 1 private const val TASK_UID = 1 @@ -244,16 +406,40 @@ class DesktopModeEventLoggerTest { private const val TASK_HEIGHT = 100 private const val TASK_WIDTH = 100 private const val TASK_COUNT = 1 + private const val DISPLAY_AREA = 1000 private val TASK_UPDATE = TaskUpdate( TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, visibleTaskCount = TASK_COUNT, ) + private val TASK_SIZE_UPDATE = TaskSizeUpdate( + resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, + inputMethod = InputMethod.UNKNOWN_INPUT_METHOD, + TASK_ID, + TASK_UID, + TASK_HEIGHT, + TASK_WIDTH, + DISPLAY_AREA, + ) + + private fun createTaskSizeUpdate( + resizeTrigger: ResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, + inputMethod: InputMethod = InputMethod.UNKNOWN_INPUT_METHOD, + ) = TaskSizeUpdate( + resizeTrigger, + inputMethod, + TASK_ID, + TASK_UID, + TASK_HEIGHT, + TASK_WIDTH, + DISPLAY_AREA, + ) + private fun createTaskUpdate( minimizeReason: MinimizeReason? = null, unminimizeReason: UnminimizeReason? = null, ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, unminimizeReason, TASK_COUNT) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index d399b20abb2a..daf7e7d5397b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -110,15 +110,20 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { shellInit = spy(ShellInit(testExecutor)) desktopModeEventLogger = mock<DesktopModeEventLogger>() - transitionObserver = - DesktopModeLoggerTransitionObserver( - context, mockShellInit, transitions, desktopModeEventLogger) - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) - verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver)) - initRunnableCaptor.value.run() - } else { - transitionObserver.onInit() + transitionObserver = DesktopModeLoggerTransitionObserver( + context, mockShellInit, transitions, desktopModeEventLogger) + val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) + verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver)) + initRunnableCaptor.value.run() + } + + @Test + fun testInitialiseVisibleTasksSystemProperty() { + ExtendedMockito.verify { + SystemProperties.set( + eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY), + eq(DesktopModeLoggerTransitionObserver + .VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE)) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt index 2b7f86f36477..935e6d052f5e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt @@ -17,8 +17,8 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo +import android.graphics.PointF import android.graphics.Rect -import android.graphics.Region import android.testing.AndroidTestingRunner import android.view.SurfaceControl import androidx.test.filters.SmallTest @@ -33,6 +33,7 @@ import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.kotlin.whenever @@ -58,13 +59,15 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS) + whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout) } @Test fun testFullscreenRegionCalculation() { createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top)) + assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, + 2 * STABLE_INSETS.top)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) @@ -75,17 +78,19 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { val toFullscreenWidth = displayLayout.width() * toFullscreenScale assertThat(testRegion.bounds).isEqualTo(Rect( (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(), - -50, + Short.MIN_VALUE.toInt(), (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(), transitionHeight)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top)) + assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, + 2 * STABLE_INSETS.top)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) + assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, + transitionHeight)) } @Test @@ -133,22 +138,19 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { } @Test - fun testToDesktopRegionCalculation() { + fun testDefaultIndicators() { createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) - val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout, - CAPTION_HEIGHT) - val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) - val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) - val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout, - splitLeftRegion, splitRightRegion, fullscreenRegion) - var testRegion = Region() - testRegion.union(DISPLAY_BOUNDS) - testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE) - testRegion.op(splitRightRegion, Region.Op.DIFFERENCE) - testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE) - assertThat(desktopRegion).isEqualTo(testRegion) + var result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f)) + assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR) + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) + result = visualIndicator.updateIndicatorType(PointF(10000f, 500f)) + assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR) + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT) + result = visualIndicator.updateIndicatorType(PointF(500f, 10000f)) + assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM) + result = visualIndicator.updateIndicatorType(PointF(500f, 10000f)) + assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) } private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index bc40d89009bc..e20f0ecb1f3b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -56,9 +56,9 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidTestingRunner::class) @ExperimentalCoroutinesApi -class DesktopModeTaskRepositoryTest : ShellTestCase() { +class DesktopRepositoryTest : ShellTestCase() { - private lateinit var repo: DesktopModeTaskRepository + private lateinit var repo: DesktopRepository private lateinit var shellInit: ShellInit private lateinit var datastoreScope: CoroutineScope @@ -71,7 +71,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - repo = DesktopModeTaskRepository(context, shellInit, persistentRepository, datastoreScope) + repo = DesktopRepository(context, shellInit, persistentRepository, datastoreScope) whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn( Desktop.getDefaultInstance() ) @@ -820,6 +820,18 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test + fun minimizeTask_withInvalidDisplay_minimizesCorrectTask() { + repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 0) + repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 0) + + repo.minimizeTask(displayId = INVALID_DISPLAY, taskId = 0) + + assertThat(repo.isMinimizedTask(taskId = 0)).isTrue() + assertThat(repo.isMinimizedTask(taskId = 1)).isFalse() + assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() + } + + @Test fun unminimizeTask_unminimizesTask() { repo.minimizeTask(displayId = 0, taskId = 0) @@ -882,8 +894,79 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { assertThat(tasks).containsExactly(1, 3).inOrder() } + @Test + fun setTaskInFullImmersiveState_savedAsInImmersiveState() { + assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse() + + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + + assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() + } + + @Test + fun removeTaskInFullImmersiveState_removedAsInImmersiveState() { + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() + + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = false) + + assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse() + } + + @Test + fun removeTaskInFullImmersiveState_otherWasImmersive_otherRemainsImmersive() { + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = false) + + assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() + } + + @Test + fun setTaskInFullImmersiveState_sameDisplay_overridesExistingFullImmersiveTask() { + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = true) + + assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse() + assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue() + } + + @Test + fun setTaskInFullImmersiveState_differentDisplay_bothAreImmersive() { + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true) + + assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() + assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue() + } + + @Test + fun removeDesktop_multipleTasks_removesAll() { + repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1) + repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2) + repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3) + // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop` + repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3) + repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 2) + repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 1) + repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2) + + val tasksBeforeRemoval = repo.removeDesktop(displayId = DEFAULT_DISPLAY) + + assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder() + assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty() + } + + @Test + fun getTaskInFullImmersiveState_byDisplay() { + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true) + + assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID)).isEqualTo(1) + assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1)).isEqualTo(2) + } - class TestListener : DesktopModeTaskRepository.ActiveTasksListener { + class TestListener : DesktopRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 override fun onActiveTasksChanged(displayId: Int) { @@ -895,7 +978,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } } - class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener { + class TestVisibilityListener : DesktopRepository.VisibleTasksListener { var visibleTasksCountOnDefaultDisplay = 0 var visibleTasksCountOnSecondaryDisplay = 0 @@ -923,4 +1006,4 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { private const val DEFAULT_USER_ID = 1000 private const val DEFAULT_DESKTOP_ID = 0 } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 29aea006c076..b3c10d64c3a3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -40,7 +40,9 @@ import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.os.Binder +import android.os.Bundle import android.os.Handler +import android.os.IBinder import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -49,6 +51,7 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.Gravity import android.view.SurfaceControl +import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE @@ -74,6 +77,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE +import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP import com.android.wm.shell.MockToken import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -96,6 +100,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplit import com.android.wm.shell.desktopmode.persistence.Desktop import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.draganddrop.DragAndDropController +import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener @@ -143,6 +148,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.Mockito.times import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce @@ -181,6 +187,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler + @Mock + lateinit var mockDesktopFullImmersiveTransitionHandler: DesktopFullImmersiveTransitionHandler @Mock lateinit var launchAdjacentController: LaunchAdjacentController @Mock lateinit var splitScreenController: SplitScreenController @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler @@ -193,13 +201,14 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var mockInteractionJankMonitor: InteractionJankMonitor @Mock private lateinit var mockSurface: SurfaceControl @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener + @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter @Mock private lateinit var mockHandler: Handler @Mock lateinit var persistentRepository: DesktopPersistentRepository private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit - private lateinit var taskRepository: DesktopModeTaskRepository + private lateinit var taskRepository: DesktopRepository private lateinit var desktopTasksLimiter: DesktopTasksLimiter private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener private lateinit var testScope: CoroutineScope @@ -230,7 +239,7 @@ class DesktopTasksControllerTest : ShellTestCase() { testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - taskRepository = DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope) + taskRepository = DesktopRepository(context, shellInit, persistentRepository, testScope) desktopTasksLimiter = DesktopTasksLimiter( transitions, @@ -258,6 +267,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller = createController() controller.setSplitScreenController(splitScreenController) + controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter shellInit.init() @@ -287,6 +297,7 @@ class DesktopTasksControllerTest : ShellTestCase() { dragAndDropTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, + mockDesktopFullImmersiveTransitionHandler, taskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, @@ -1348,6 +1359,32 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveTaskToFront_backgroundTask_launchesTask() { + val task = createTaskInfo(1) + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) + + controller.moveTaskToFront(task.taskId) + + val wct = getLatestWct(type = TRANSIT_OPEN) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + } + + @Test + fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() { + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + val task = createTaskInfo(1001) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) + + controller.moveTaskToFront(task.taskId) + + val wct = getLatestWct(type = TRANSIT_OPEN) + assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize + wct.assertReorderAt(0, freeformTasks[0], toTop = false) + wct.assertLaunchTaskAt(1, task.taskId, WINDOWING_MODE_FREEFORM) + } + + @Test fun moveToNextDisplay_noOtherDisplays() { whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -1507,75 +1544,142 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun onDesktopWindowMinimize_noActiveTask_doesntUpdateTransaction() { - val wct = WindowContainerTransaction() - controller.onDesktopWindowMinimize(wct, taskId = 1) - // Nothing happens. - assertThat(wct.hierarchyOps).isEmpty() + fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() { + val task = setUpFreeformTask(active = false) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } } @Test - fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntUpdateTransaction() { - val task = setUpFreeformTask() - val wct = WindowContainerTransaction() - controller.onDesktopWindowMinimize(wct, taskId = task.taskId) - // Nothing happens. - assertThat(wct.hierarchyOps).isEmpty() + fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { + val task = setUpFreeformTask(active = true) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK + } } @Test fun onDesktopWindowMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() { val task = setUpFreeformTask() + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) val wallpaperToken = MockToken().token() taskRepository.wallpaperActivityToken = wallpaperToken - val wct = WindowContainerTransaction() // The only active task is being minimized. - controller.onDesktopWindowMinimize(wct, taskId = task.taskId) + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) // Adds remove wallpaper operation - wct.assertRemoveAt(index = 0, wallpaperToken) + captor.value.assertRemoveAt(index = 0, wallpaperToken) } @Test - fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntUpdateTransaction() { + fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() { val task = setUpFreeformTask() + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) val wallpaperToken = MockToken().token() taskRepository.wallpaperActivityToken = wallpaperToken taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) - val wct = WindowContainerTransaction() // The only active task is already minimized. - controller.onDesktopWindowMinimize(wct, taskId = task.taskId) - // Doesn't modify transaction - assertThat(wct.hierarchyOps).isEmpty() + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } } @Test - fun onDesktopWindowMinimize_multipleActiveTasks_doesntUpdateTransaction() { - val task1 = setUpFreeformTask() - setUpFreeformTask() + fun onDesktopWindowMinimize_multipleActiveTasks_doesntRemoveWallpaper() { + val task1 = setUpFreeformTask(active = true) + setUpFreeformTask(active = true) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) val wallpaperToken = MockToken().token() taskRepository.wallpaperActivityToken = wallpaperToken - val wct = WindowContainerTransaction() - controller.onDesktopWindowMinimize(wct, taskId = task1.taskId) - // Doesn't modify transaction - assertThat(wct.hierarchyOps).isEmpty() + controller.minimizeTask(task1) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } } @Test fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() + val task1 = setUpFreeformTask(active = true) + val task2 = setUpFreeformTask(active = true) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) val wallpaperToken = MockToken().token() taskRepository.wallpaperActivityToken = wallpaperToken taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) - val wct = WindowContainerTransaction() // task1 is the only visible task as task2 is minimized. - controller.onDesktopWindowMinimize(wct, taskId = task1.taskId) + controller.minimizeTask(task1) // Adds remove wallpaper operation - wct.assertRemoveAt(index = 0, wallpaperToken) + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + // Adds remove wallpaper operation + captor.value.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowMinimize_triesToExitImmersive() { + val task = setUpFreeformTask() + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + + controller.minimizeTask(task) + + verify(mockDesktopFullImmersiveTransitionHandler).exitImmersiveIfApplicable(any(), eq(task)) + } + + @Test + fun onDesktopWindowMinimize_invokesImmersiveTransitionStartCallback() { + val task = setUpFreeformTask() + val transition = Binder() + val runOnTransit = RunOnStartTransitionCallback() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + whenever(mockDesktopFullImmersiveTransitionHandler.exitImmersiveIfApplicable(any(), eq(task))) + .thenReturn(runOnTransit) + + controller.minimizeTask(task) + + assertThat(runOnTransit.invocations).isEqualTo(1) + assertThat(runOnTransit.lastInvoked).isEqualTo(transition) } @Test @@ -2073,11 +2177,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, - ) - fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) + fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -2110,8 +2211,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() { + fun handleRequest_backTransition_singleTaskNoToken_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -2120,11 +2220,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) + fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() { val task = setUpFreeformTask() taskRepository.wallpaperActivityToken = MockToken().token() @@ -2147,11 +2244,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_backTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) + fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -2163,7 +2257,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() { + fun handleRequest_backTransition_multipleTasks_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -2209,11 +2303,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_withBackNav_removesWallpaper() { + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) + fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -2229,11 +2320,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) + fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -2242,22 +2330,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() { - val task = setUpFreeformTask() - - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) - - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task.token) - } - - @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_singleTaskNoToken_noBackNav_doesNotHandle() { + fun handleRequest_closeTransition_singleTaskNoToken_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -2266,11 +2340,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() { val task = setUpFreeformTask() taskRepository.wallpaperActivityToken = MockToken().token() @@ -2280,26 +2351,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_singleTaskWithToken_removesWallpaperAndTask() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - result.assertRemoveAt(index = 1, task.token) - } - - @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_noBackNav_removesWallpaper() { + fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -2311,11 +2364,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -2326,25 +2376,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_multipleTasks_withWallpaper_withBackNav_removesTask() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) - - assertNotNull(result, "Should handle request") - result.assertRemoveAt(index = 0, task1.token) - } - - @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksFlagEnabled_noBackNav_doesNotHandle() { + fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -2355,28 +2388,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - result.assertRemoveAt(index = 1, task1.token) - } - - @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() { + fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -2390,28 +2403,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_multipleTasksOneNonMinimized_removesWallpaperAndTask() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - result.assertRemoveAt(index = 1, task1.token) - } - - @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() { + fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -2425,11 +2418,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_closeTransition_minimizadTask_withWallpaper_withBackNav_removesWallpaper() { + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) + fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -2545,6 +2535,41 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun removeDesktop_multipleTasks_removesAll() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + + controller.removeDesktop(displayId = DEFAULT_DISPLAY) + + val wct = getLatestWct(TRANSIT_CLOSE) + assertThat(wct.hierarchyOps).hasSize(3) + wct.assertRemoveAt(index = 0, task1.token) + wct.assertRemoveAt(index = 1, task2.token) + wct.assertRemoveAt(index = 2, task3.token) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null) + + controller.removeDesktop(displayId = DEFAULT_DISPLAY) + + val wct = getLatestWct(TRANSIT_CLOSE) + assertThat(wct.hierarchyOps).hasSize(2) + wct.assertRemoveAt(index = 0, task1.token) + wct.assertRemoveAt(index = 1, task2.token) + verify(recentTasksController).removeBackgroundTask(task3.taskId) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { val spyController = spy(controller) @@ -2729,13 +2754,17 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() { val task = setUpFreeformTask() + val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) - controller.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000)) + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000)) - controller.onDragPositioningEnd( + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) + spyController.onDragPositioningEnd( task, mockSurface, Point(100, -100), /* position */ @@ -2865,6 +2894,108 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun newWindow_fromFullscreenOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenNewWindow(task) + verify(splitScreenController) + .startIntent(any(), anyInt(), any(), any(), + optionsCaptor.capture(), anyOrNull()) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun newWindow_fromSplitOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpSplitScreenTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenNewWindow(task) + verify(splitScreenController) + .startIntent( + any(), anyInt(), any(), any(), + optionsCaptor.capture(), anyOrNull() + ) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun newWindow_fromFreeformAddsNewWindow() { + setUpLandscapeDisplay() + val task = setUpFreeformTask() + val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + runOpenNewWindow(task) + verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull()) + assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions) + .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + } + + private fun runOpenNewWindow(task: RunningTaskInfo) { + markTaskVisible(task) + task.baseActivity = mock(ComponentName::class.java) + task.isFocused = true + runningTasks.add(task) + controller.openNewWindow(task) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFullscreenOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask() + val taskToRequest = setUpFreeformTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenInstance(task, taskToRequest.taskId) + verify(splitScreenController) + .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull()) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromSplitOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpSplitScreenTask() + val taskToRequest = setUpFreeformTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenInstance(task, taskToRequest.taskId) + verify(splitScreenController) + .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull()) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFreeformAddsNewWindow() { + setUpLandscapeDisplay() + val task = setUpFreeformTask() + val taskToRequest = setUpFreeformTask() + val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + runOpenInstance(task, taskToRequest.taskId) + verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull()) + assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions) + .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + } + + private fun runOpenInstance( + callingTask: RunningTaskInfo, + requestedTaskId: Int + ) { + markTaskVisible(callingTask) + callingTask.baseActivity = mock(ComponentName::class.java) + callingTask.isFocused = true + runningTasks.add(callingTask) + controller.openInstance(callingTask, requestedTaskId) + } + + @Test fun toggleBounds_togglesToStableBounds() { val bounds = Rect(0, 0, 100, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) @@ -3098,6 +3229,161 @@ class DesktopTasksControllerTest : ShellTestCase() { Rect()) } + @Test + fun shellController_registersUserChangeListener() { + verify(shellController, times(1)).addUserChangeListener(any()) + } + + @Test + fun toggleImmersive_enter_movesToImmersive() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, false /* immersive */) + + controller.toggleDesktopTaskFullImmersiveState(task) + + verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToImmersive(task) + } + + @Test + fun toggleImmersive_exit_movesToNonImmersive() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, true /* immersive */) + + controller.toggleDesktopTaskFullImmersiveState(task) + + verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToNonImmersive(task) + } + + @Test + @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true) + + task.requestedVisibleTypes = WindowInsets.Type.statusBars() + controller.onTaskInfoChanged(task) + + verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToNonImmersive(task) + } + + @Test + @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false) + + task.requestedVisibleTypes = WindowInsets.Type.statusBars() + controller.onTaskInfoChanged(task) + + verify(mockDesktopFullImmersiveTransitionHandler, never()).moveTaskToNonImmersive(task) + } + + @Test + fun moveTaskToDesktop_background_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = true) + val wct = WindowContainerTransaction() + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever(mockDesktopFullImmersiveTransitionHandler + .exitImmersiveIfApplicable(wct, task.displayId)).thenReturn(runOnStartTransit) + whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) + + controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) + + verify(mockDesktopFullImmersiveTransitionHandler).exitImmersiveIfApplicable(wct, task.displayId) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun moveTaskToDesktop_foreground_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = false) + val wct = WindowContainerTransaction() + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever(mockDesktopFullImmersiveTransitionHandler + .exitImmersiveIfApplicable(wct, task.displayId)).thenReturn(runOnStartTransit) + whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) + + controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) + + verify(mockDesktopFullImmersiveTransitionHandler).exitImmersiveIfApplicable(wct, task.displayId) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun moveTaskToFront_background_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = true) + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever(mockDesktopFullImmersiveTransitionHandler + .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit) + whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition) + + controller.moveTaskToFront(task.taskId) + + verify(mockDesktopFullImmersiveTransitionHandler) + .exitImmersiveIfApplicable(any(), eq(task.displayId)) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun moveTaskToFront_foreground_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = false) + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever(mockDesktopFullImmersiveTransitionHandler + .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit) + whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition) + + controller.moveTaskToFront(task.taskId) + + verify(mockDesktopFullImmersiveTransitionHandler) + .exitImmersiveIfApplicable(any(), eq(task.displayId)) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun handleRequest_freeformLaunchToDesktop_attemptsImmersiveExit() { + markTaskVisible(setUpFreeformTask()) + val task = setUpFreeformTask() + markTaskVisible(task) + val binder = Binder() + + controller.handleRequest(binder, createTransition(task)) + + verify(mockDesktopFullImmersiveTransitionHandler) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) + } + + @Test + fun handleRequest_fullscreenLaunchToDesktop_attemptsImmersiveExit() { + setUpFreeformTask() + val task = setUpFullscreenTask() + val binder = Binder() + + controller.handleRequest(binder, createTransition(task)) + + verify(mockDesktopFullImmersiveTransitionHandler) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) + } + + private class RunOnStartTransitionCallback : ((IBinder) -> Unit) { + var invocations = 0 + private set + var lastInvoked: IBinder? = null + private set + + override fun invoke(transition: IBinder) { + invocations++ + lastInvoked = transition + } + } + + private fun RunOnStartTransitionCallback.assertOnlyInvocation(transition: IBinder) { + assertThat(invocations).isEqualTo(1) + assertThat(lastInvoked).isEqualTo(transition) + } + /** * Assert that an unhandled drag event launches a PendingIntent with the * windowing mode and bounds we are expecting. @@ -3176,18 +3462,27 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpFreeformTask( displayId: Int = DEFAULT_DISPLAY, bounds: Rect? = null, - active: Boolean = true + active: Boolean = true, + background: Boolean = false, ): RunningTaskInfo { val task = createFreeformTask(displayId, bounds) val activityInfo = ActivityInfo() task.topActivityInfo = activityInfo - whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + if (background) { + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) + whenever(recentTasksController.findTaskInBackground(task.taskId)) + .thenReturn(createTaskInfo(task.taskId)) + } else { + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + } if (active) { taskRepository.addActiveTask(displayId, task.taskId) taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true) } taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId) - runningTasks.add(task) + if (!background) { + runningTasks.add(task) + } return task } @@ -3441,6 +3736,21 @@ private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowC assertThat(op.container).isEqualTo(token.asBinder()) } +private fun WindowContainerTransaction.assertNoRemoveAt(index: Int, token: WindowContainerToken) { + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(op.container).isEqualTo(token.asBinder()) +} + +private fun WindowContainerTransaction.hasRemoveAt(index: Int, token: WindowContainerToken) { + + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(op.container).isEqualTo(token.asBinder()) +} + private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) { assertIndexInBounds(index) val op = hierarchyOps[index] diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 045e07796cb8..596b76dbdb2e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.os.Binder import android.os.Handler +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY @@ -33,6 +35,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor @@ -89,7 +92,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { private lateinit var mockitoSession: StaticMockitoSession private lateinit var desktopTasksLimiter: DesktopTasksLimiter - private lateinit var desktopTaskRepo: DesktopModeTaskRepository + private lateinit var desktopTaskRepo: DesktopRepository private lateinit var shellInit: ShellInit private lateinit var testScope: CoroutineScope @@ -103,7 +106,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) desktopTaskRepo = - DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope) + DesktopRepository(context, shellInit, persistentRepository, testScope) desktopTasksLimiter = DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT, interactionJankMonitor, mContext, handler) @@ -243,6 +246,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() { desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1) desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2) @@ -256,6 +260,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() { val wct = WindowContainerTransaction() desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( @@ -265,6 +270,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -283,6 +289,20 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId) + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + + val wct = WindowContainerTransaction() + desktopTasksLimiter.leftoverMinimizedTasksRemover.onActiveTasksChanged(DEFAULT_DISPLAY) + + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() { (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } @@ -291,7 +311,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded( displayId = DEFAULT_DISPLAY, wct = wct, - newFrontTaskInfo = setUpFreeformTask()) + newFrontTaskId = setUpFreeformTask().taskId) assertThat(minimizedTaskId).isNull() assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added @@ -307,7 +327,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded( displayId = DEFAULT_DISPLAY, wct = wct, - newFrontTaskInfo = setUpFreeformTask()) + newFrontTaskId = setUpFreeformTask().taskId) assertThat(minimizedTaskId).isEqualTo(tasks.first()) assertThat(wct.hierarchyOps.size).isEqualTo(1) @@ -325,7 +345,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded( displayId = 0, wct = wct, - newFrontTaskInfo = setUpFreeformTask()) + newFrontTaskId = setUpFreeformTask().taskId) assertThat(minimizedTaskId).isNull() assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index c989d1640f80..fe87aa88a8db 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -18,28 +18,42 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.ComponentName import android.content.Context import android.content.Intent +import android.os.IBinder import android.platform.test.annotations.EnableFlags import android.view.Display.DEFAULT_DISPLAY +import android.view.WindowManager +import android.view.WindowManager.TRANSIT_CLOSE +import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.window.IWindowContainerToken import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.WindowContainerToken +import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags +import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.eq +import org.mockito.ArgumentMatchers.isA import org.mockito.Mockito import org.mockito.kotlin.any +import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.spy @@ -60,7 +74,7 @@ class DesktopTasksTransitionObserverTest { private val transitions = mock<Transitions>() private val context = mock<Context>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() - private val taskRepository = mock<DesktopModeTaskRepository>() + private val taskRepository = mock<DesktopRepository>() private lateinit var transitionObserver: DesktopTasksTransitionObserver private lateinit var shellInit: ShellInit @@ -110,6 +124,45 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun removeTasks_onTaskFullscreenLaunch_taskRemovedFromRepo() { + val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN) + whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1) + whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true) + + transitionObserver.onTransitionReady( + transition = mock(), + info = createOpenTransition(task), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId) + verify(taskRepository).removeFreeformTask(task.displayId, task.taskId) + } + + @Test + fun closeLastTask_wallpaperTokenExists_wallpaperIsRemoved() { + val mockTransition = Mockito.mock(IBinder::class.java) + val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) + val wallpaperToken = MockToken().token() + whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1) + whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken) + + transitionObserver.onTransitionReady( + transition = mockTransition, + info = createCloseTransition(task), + startTransaction = mock(), + finishTransaction = mock(), + ) + transitionObserver.onTransitionFinished(mockTransition, false) + + val wct = getLatestWct(type = TRANSIT_CLOSE) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertRemoveAt(index = 0, wallpaperToken) + } + private fun createBackNavigationTransition( task: RunningTaskInfo? ): TransitionInfo { @@ -125,11 +178,68 @@ class DesktopTasksTransitionObserverTest { } } - private fun createTaskInfo(id: Int) = + private fun createOpenTransition( + task: RunningTaskInfo? + ): TransitionInfo { + return TransitionInfo(TRANSIT_OPEN, 0 /* flags */).apply { + addChange( + Change(mock(), mock()).apply { + mode = TRANSIT_OPEN + parent = null + taskInfo = task + flags = flags + } + ) + } + } + + private fun createCloseTransition( + task: RunningTaskInfo? + ): TransitionInfo { + return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply { + addChange( + Change(mock(), mock()).apply { + mode = TRANSIT_CLOSE + parent = null + taskInfo = task + flags = flags + } + ) + } + } + + private fun getLatestWct( + @WindowManager.TransitionType type: Int = TRANSIT_OPEN, + handlerClass: Class<out Transitions.TransitionHandler>? = null + ): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (handlerClass == null) { + Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull()) + } else { + Mockito.verify(transitions) + .startTransition(eq(type), arg.capture(), isA(handlerClass)) + } + return arg.value + } + + private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) { + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(op.container).isEqualTo(token.asBinder()) + } + + private fun WindowContainerTransaction.assertIndexInBounds(index: Int) { + assertWithMessage("WCT does not have a hierarchy operation at index $index") + .that(hierarchyOps.size) + .isGreaterThan(index) + } + + private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) = RunningTaskInfo().apply { taskId = id displayId = DEFAULT_DISPLAY - configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + configuration.windowConfiguration.windowingMode = windowingMode token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java)) baseIntent = Intent().apply { component = ComponentName("package", "component.name") diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index d9387d2f08dd..230f7e6912ee 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -581,7 +581,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) ) .thenReturn(token) - handler.startDragToDesktopTransition(task.taskId, dragAnimator) + handler.startDragToDesktopTransition(task, dragAnimator) return token } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt new file mode 100644 index 000000000000..1e105d9588ab --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2024 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.desktopmode.education + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import android.testing.TestableContext +import androidx.test.filters.SmallTest +import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.window.flags.Flags +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.desktopmode.CaptionState +import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository +import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_DELAY_MILLIS +import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_TIMEOUT_MILLIS +import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource +import com.android.wm.shell.util.createAppHandleState +import com.android.wm.shell.util.createAppHeaderState +import com.android.wm.shell.util.createWindowingEducationProto +import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Tests of [AppHandleEducationController] Usage: atest AppHandleEducationControllerTest */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) +class AppHandleEducationControllerTest : ShellTestCase() { + @JvmField + @Rule + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!! + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + private lateinit var educationController: AppHandleEducationController + private lateinit var testableContext: TestableContext + private val testScope = TestScope() + private val testDataStoreFlow = MutableStateFlow(createWindowingEducationProto()) + private val testCaptionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption) + private val educationConfigCaptor = + argumentCaptor<DesktopWindowingEducationTooltipController.EducationViewConfig>() + @Mock private lateinit var mockEducationFilter: AppHandleEducationFilter + @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository + @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository + @Mock private lateinit var mockTooltipController: DesktopWindowingEducationTooltipController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + Dispatchers.setMain(StandardTestDispatcher(testScope.testScheduler)) + testableContext = TestableContext(mContext) + whenever(mockDataStoreRepository.dataStoreFlow).thenReturn(testDataStoreFlow) + whenever(mockCaptionHandleRepository.captionStateFlow).thenReturn(testCaptionStateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + + educationController = + AppHandleEducationController( + testableContext, + mockEducationFilter, + mockDataStoreRepository, + mockCaptionHandleRepository, + mockTooltipController, + testScope.backgroundScope, + Dispatchers.Main) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleVisible_shouldCallShowEducationTooltip() = + testScope.runTest { + // App handle is visible. Should show education tooltip. + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_flagDisabled_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle visible but education aconfig flag disabled, should not show education + // tooltip. + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_shouldShowAppHandleEducationReturnsFalse_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle is visible but [shouldShowAppHandleEducation] api returns false, should not + // show education tooltip. + setShouldShowAppHandleEducation(false) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleNotVisible_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle is not visible, should not show education tooltip. + setShouldShowAppHandleEducation(true) + + // Simulate app handle is not visible. + testCaptionStateFlow.value = CaptionState.NoCaption + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_educationViewedAlready_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle is visible but education has been viewed before. Should not show education + // tooltip. + // Mark education viewed. + testDataStoreFlow.value = + createWindowingEducationProto(educationViewedTimestampMillis = 123L) + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleExpanded_shouldMarkFeatureViewed() = + testScope.runTest { + setShouldShowAppHandleEducation(false) + + // Simulate app handle visible and expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for some time before verifying + waitForBufferDelay() + + verify(mockDataStoreRepository, times(1)).updateFeatureUsedTimestampMillis(eq(true)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_showFirstTooltip_shouldMarkEducationViewed() = + testScope.runTest { + // App handle is visible. Should show education tooltip. + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockDataStoreRepository, times(1)).updateEducationViewedTimestampMillis(eq(true)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded. Should show second education + // tooltip. + showAndDismissFirstTooltip() + + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + // [showEducationTooltip] should be called twice, once for each tooltip. + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded after timeout. Should not show + // second education tooltip. + showAndDismissFirstTooltip() + + // Wait for timeout to occur, after this timeout we should not listen for further triggers + // anymore. + advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) + runCurrent() + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + // [showEducationTooltip] should be called once, just for the first tooltip. + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded twice. Should show second + // education tooltip only once. + showAndDismissFirstTooltip() + + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + // Simulate app handle being expanded twice. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + waitForBufferDelay() + + // [showEducationTooltip] should not be called thrice, even if app handle was expanded + // twice. Should be called twice, once for each tooltip. + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() = + testScope.runTest { + // After first tooltip is dismissed, app handle is not expanded. Should not show second + // education tooltip. + showAndDismissFirstTooltip() + + // Simulate app handle visible but not expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) + // Wait for next tooltip to showup. + waitForBufferDelay() + + // [showEducationTooltip] should be called once, just for the first tooltip. + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible. Should show third + // education tooltip. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible after timeout. Should not + // show third education tooltip. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Wait for timeout to occur, after this timeout we should not listen for further triggers + // anymore. + advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) + runCurrent() + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible twice. Should show third + // education tooltip only once. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible but expanded. Should not + // show third education tooltip. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState(isHeaderMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun setAppHandleEducationTooltipCallbacks_onAppHandleTooltipClicked_callbackInvoked() = + testScope.runTest { + // App handle is visible. Should show education tooltip. + setShouldShowAppHandleEducation(true) + val mockOpenHandleMenuCallback: (Int) -> Unit = mock() + val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() + educationController.setAppHandleEducationTooltipCallbacks( + mockOpenHandleMenuCallback, mockToDesktopModeCallback) + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, atLeastOnce()) + .showEducationTooltip(educationConfigCaptor.capture(), any()) + educationConfigCaptor.lastValue.onEducationClickAction.invoke() + + verify(mockOpenHandleMenuCallback, times(1)).invoke(any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded. Should show second education + // tooltip. + showAndDismissFirstTooltip() + val mockOpenHandleMenuCallback: (Int) -> Unit = mock() + val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() + educationController.setAppHandleEducationTooltipCallbacks( + mockOpenHandleMenuCallback, mockToDesktopModeCallback) + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, atLeastOnce()) + .showEducationTooltip(educationConfigCaptor.capture(), any()) + educationConfigCaptor.lastValue.onEducationClickAction.invoke() + + verify(mockToDesktopModeCallback, times(1)).invoke(any(), any()) + } + + private suspend fun TestScope.showAndDismissFirstTooltip() { + setShouldShowAppHandleEducation(true) + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) + // Wait for first tooltip to showup. + waitForBufferDelay() + // [shouldShowAppHandleEducation] should return false as education has been viewed + // before. + setShouldShowAppHandleEducation(false) + // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. + captureAndInvokeOnDismissAction() + } + + private fun TestScope.showAndDismissSecondTooltip() { + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. + captureAndInvokeOnDismissAction() + } + + private fun captureAndInvokeOnDismissAction() { + verify(mockTooltipController, atLeastOnce()) + .showEducationTooltip(educationConfigCaptor.capture(), any()) + educationConfigCaptor.lastValue.onDismissAction.invoke() + } + + private suspend fun setShouldShowAppHandleEducation(shouldShowAppHandleEducation: Boolean) = + whenever(mockEducationFilter.shouldShowAppHandleEducation(any())) + .thenReturn(shouldShowAppHandleEducation) + + /** + * Class under test waits for some seconds before showing education, simulate advance time before + * verifying or moving forward + */ + private fun TestScope.waitForBufferDelay() { + advanceTimeBy(APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS) + runCurrent() + } + + private companion object { + val APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS: Long = APP_HANDLE_EDUCATION_DELAY_MILLIS + 1000L + val APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS: Long = + APP_HANDLE_EDUCATION_TIMEOUT_MILLIS + 1000L + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt index 765021fbbd3d..c2865441d7a6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt @@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto +import com.android.wm.shell.util.GMAIL_PACKAGE_NAME import com.android.wm.shell.util.createWindowingEducationProto import com.google.common.truth.Truth.assertThat import java.io.File @@ -108,8 +109,25 @@ class AppHandleEducationDatastoreRepositoryTest { assertThat(result).isEqualTo(windowingEducationProto) } + @Test + fun updateEducationViewedTimestampMillis_updatesDatastoreProto() = + runTest(StandardTestDispatcher()) { + datastoreRepository.updateEducationViewedTimestampMillis(true) + + val result = testDatastore.data.first().hasEducationViewedTimestampMillis() + assertThat(result).isEqualTo(true) + } + + @Test + fun updateFeatureUsedTimestampMillis_updatesDatastoreProto() = + runTest(StandardTestDispatcher()) { + datastoreRepository.updateFeatureUsedTimestampMillis(true) + + val result = testDatastore.data.first().hasFeatureUsedTimestampMillis() + assertThat(result).isEqualTo(true) + } + companion object { - private const val GMAIL_PACKAGE_NAME = "com.google.android.gm" private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb" } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt index c0d71c0bf5db..ac994248c962 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt @@ -26,6 +26,10 @@ import androidx.test.filters.SmallTest import com.android.wm.shell.R import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository +import com.android.wm.shell.util.GMAIL_PACKAGE_NAME +import com.android.wm.shell.util.YOUTUBE_PACKAGE_NAME +import com.android.wm.shell.util.createAppHandleState +import com.android.wm.shell.util.createTaskInfo import com.android.wm.shell.util.createWindowingEducationProto import com.google.common.truth.Truth.assertThat import kotlin.Int.Companion.MAX_VALUE @@ -38,6 +42,8 @@ import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +/** Tests of [AppHandleEducationFilter] + * Usage: atest AppHandleEducationFilterTest */ @SmallTest @RunWith(AndroidTestingRunner::class) class AppHandleEducationFilterTest : ShellTestCase() { @@ -76,7 +82,7 @@ class AppHandleEducationFilterTest : ShellTestCase() { appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(GMAIL_PACKAGE_NAME) + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) assertThat(result).isTrue() } @@ -91,9 +97,11 @@ class AppHandleEducationFilterTest : ShellTestCase() { createWindowingEducationProto( appUsageStats = mapOf(YOUTUBE_PACKAGE_NAME to 4), appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) + val captionState = + createAppHandleState(createTaskInfo(runningTaskPackageName = YOUTUBE_PACKAGE_NAME)) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(YOUTUBE_PACKAGE_NAME) + val result = educationFilter.shouldShowAppHandleEducation(captionState) assertThat(result).isFalse() } @@ -110,7 +118,7 @@ class AppHandleEducationFilterTest : ShellTestCase() { appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(GMAIL_PACKAGE_NAME) + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) assertThat(result).isFalse() } @@ -125,7 +133,7 @@ class AppHandleEducationFilterTest : ShellTestCase() { appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(GMAIL_PACKAGE_NAME) + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) assertThat(result).isFalse() } @@ -140,7 +148,7 @@ class AppHandleEducationFilterTest : ShellTestCase() { appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(GMAIL_PACKAGE_NAME) + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) assertThat(result).isFalse() } @@ -156,7 +164,7 @@ class AppHandleEducationFilterTest : ShellTestCase() { appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(GMAIL_PACKAGE_NAME) + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) assertThat(result).isFalse() } @@ -179,15 +187,26 @@ class AppHandleEducationFilterTest : ShellTestCase() { .thenReturn(mapOf(GMAIL_PACKAGE_NAME to UsageStats().apply { mAppLaunchCount = 2 })) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(GMAIL_PACKAGE_NAME) + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) // Result should be false as queried usage stats should be considered to determine the result // instead of cached stats assertThat(result).isFalse() } - companion object { - private const val GMAIL_PACKAGE_NAME = "com.google.android.gm" - private const val YOUTUBE_PACKAGE_NAME = "com.google.android.youtube" + @Test + fun shouldShowAppHandleEducation_appHandleMenuExpanded_returnsFalse() = runTest { + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) + // Simulate app handle menu is expanded + val captionState = createAppHandleState(isHandleMenuExpanded = true) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(captionState) + + // We should not show app handle education if app menu is expanded + assertThat(result).isFalse() } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java index 763d0153071e..f95b0d1e7287 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java @@ -18,15 +18,19 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.INVALID_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.platform.test.annotations.EnableFlags; import android.view.SurfaceControl; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -37,7 +41,8 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.LaunchAdjacentController; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -69,7 +74,9 @@ public final class FreeformTaskListenerTests extends ShellTestCase { @Mock private SurfaceControl mMockSurfaceControl; @Mock - private DesktopModeTaskRepository mDesktopModeTaskRepository; + private DesktopRepository mDesktopRepository; + @Mock + private DesktopTasksController mDesktopTasksController; @Mock private LaunchAdjacentController mLaunchAdjacentController; private FreeformTaskListener mFreeformTaskListener; @@ -85,7 +92,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mContext, mShellInit, mTaskOrganizer, - Optional.of(mDesktopModeTaskRepository), + Optional.of(mDesktopRepository), + Optional.of(mDesktopTasksController), mLaunchAdjacentController, mWindowDecorViewModel); } @@ -98,7 +106,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onFocusTaskChanged(task); - verify(mDesktopModeTaskRepository) + verify(mDesktopRepository) .addOrMoveFreeformTaskToTop(task.displayId, task.taskId); } @@ -110,7 +118,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onFocusTaskChanged(fullscreenTask); - verify(mDesktopModeTaskRepository, never()) + verify(mDesktopRepository, never()) .addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId); } @@ -139,6 +147,53 @@ public final class FreeformTaskListenerTests extends ShellTestCase { verify(mLaunchAdjacentController).setLaunchAdjacentEnabled(true); } + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + public void onTaskVanished_nonClosingTask_isMinimized() { + ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder() + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + task.isVisible = true; + + mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); + + task.isVisible = false; + task.displayId = INVALID_DISPLAY; + mFreeformTaskListener.onTaskVanished(task); + + verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId); + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + public void onTaskVanished_closingTask_isNotMinimized() { + ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder() + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + task.isVisible = true; + + mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); + + when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true); + task.isVisible = false; + task.displayId = INVALID_DISPLAY; + mFreeformTaskListener.onTaskVanished(task); + + verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId); + verify(mDesktopRepository).removeClosingTask(task.taskId); + verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId); + } + + @Test + public void onTaskInfoChanged_withDesktopController_forwards() { + ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder() + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + task.isVisible = true; + mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); + + mFreeformTaskListener.onTaskInfoChanged(task); + + verify(mDesktopTasksController).onTaskInfoChanged(task); + } + @After public void tearDown() { mMockitoSession.finishMocking(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java index 499e339bc682..7ae0bcd13681 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java @@ -17,8 +17,11 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -30,6 +33,8 @@ import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.IBinder; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.SurfaceControl; import android.window.IWindowContainerToken; import android.window.TransitionInfo; @@ -37,7 +42,10 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; +import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -48,18 +56,27 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** - * Tests of {@link FreeformTaskTransitionObserver} + * Tests for {@link FreeformTaskTransitionObserver}. */ @SmallTest public class FreeformTaskTransitionObserverTest { + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private ShellInit mShellInit; @Mock private Transitions mTransitions; @Mock + private DesktopFullImmersiveTransitionHandler mDesktopFullImmersiveTransitionHandler; + @Mock private WindowDecorViewModel mWindowDecorViewModel; + @Mock + private TaskChangeListener mTaskChangeListener; + @Mock + private FocusTransitionObserver mFocusTransitionObserver; private FreeformTaskTransitionObserver mTransitionObserver; @@ -69,30 +86,29 @@ public class FreeformTaskTransitionObserverTest { PackageManager pm = mock(PackageManager.class); doReturn(true).when(pm).hasSystemFeature( - PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT); + PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT); final Context context = mock(Context.class); doReturn(pm).when(context).getPackageManager(); mTransitionObserver = new FreeformTaskTransitionObserver( - context, mShellInit, mTransitions, mWindowDecorViewModel); - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass( - Runnable.class); - verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), - same(mTransitionObserver)); - initRunnableCaptor.getValue().run(); - } else { - mTransitionObserver.onInit(); - } + context, mShellInit, mTransitions, + Optional.of(mDesktopFullImmersiveTransitionHandler), + mWindowDecorViewModel, Optional.of(mTaskChangeListener), mFocusTransitionObserver); + + final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass( + Runnable.class); + verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), + same(mTransitionObserver)); + initRunnableCaptor.getValue().run(); } @Test - public void testRegistersObserverAtInit() { + public void init_registersObserver() { verify(mTransitions).registerObserver(same(mTransitionObserver)); } @Test - public void testCreatesWindowDecorOnOpenTransition_freeform() { + public void openTransition_createsWindowDecor() { final TransitionInfo.Change change = createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM); final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) @@ -109,7 +125,71 @@ public class FreeformTaskTransitionObserverTest { } @Test - public void testPreparesWindowDecorOnCloseTransition_freeform() { + public void openTransition_notifiesOnTaskOpening() { + final TransitionInfo.Change change = + createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(change).build(); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mTaskChangeListener).onTaskOpening(change.getTaskInfo()); + } + + @Test + public void toFrontTransition_notifiesOnTaskMovingToFront() { + final TransitionInfo.Change change = + createChange(TRANSIT_TO_FRONT, /* taskId= */ 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, /* flags= */ 0) + .addChange(change).build(); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mTaskChangeListener).onTaskMovingToFront(change.getTaskInfo()); + } + + @Test + public void toBackTransition_notifiesOnTaskMovingToBack() { + final TransitionInfo.Change change = + createChange(TRANSIT_TO_BACK, /* taskId= */ 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_BACK, /* flags= */ 0) + .addChange(change).build(); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mTaskChangeListener).onTaskMovingToBack(change.getTaskInfo()); + } + + @Test + public void changeTransition_notifiesOnTaskChanging() { + final TransitionInfo.Change change = + createChange(TRANSIT_CHANGE, /* taskId= */ 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, /* flags= */ 0) + .addChange(change).build(); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mTaskChangeListener).onTaskChanging(change.getTaskInfo()); + } + + @Test + public void closeTransition_preparesWindowDecor() { final TransitionInfo.Change change = createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0) @@ -126,7 +206,23 @@ public class FreeformTaskTransitionObserverTest { } @Test - public void testDoesntCloseWindowDecorDuringCloseTransition() throws Exception { + public void closeTransition_notifiesOnTaskClosing() { + final TransitionInfo.Change change = + createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0) + .addChange(change).build(); + + final IBinder transition = mock(IBinder.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + mTransitionObserver.onTransitionStarting(transition); + + verify(mTaskChangeListener).onTaskClosing(change.getTaskInfo()); + } + + @Test + public void closeTransition_doesntCloseWindowDecorDuringTransition() throws Exception { final TransitionInfo.Change change = createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0) @@ -142,7 +238,7 @@ public class FreeformTaskTransitionObserverTest { } @Test - public void testClosesWindowDecorAfterCloseTransition() throws Exception { + public void closeTransition_closesWindowDecorAfterTransition() throws Exception { final TransitionInfo.Change change = createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0) @@ -161,7 +257,7 @@ public class FreeformTaskTransitionObserverTest { } @Test - public void testClosesMergedWindowDecorationAfterTransitionFinishes() throws Exception { + public void transitionFinished_closesMergedWindowDecoration() throws Exception { // The playing transition final TransitionInfo.Change change1 = createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM); @@ -192,7 +288,7 @@ public class FreeformTaskTransitionObserverTest { } @Test - public void testClosesAllWindowDecorsOnTransitionMergeAfterCloseTransitions() throws Exception { + public void closeTransition_closesWindowDecorsOnTransitionMerge() throws Exception { // The playing transition final TransitionInfo.Change change1 = createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM); @@ -223,6 +319,19 @@ public class FreeformTaskTransitionObserverTest { verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo()); } + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void onTransitionReady_forwardsToDesktopImmersiveHandler() { + final IBinder transition = mock(IBinder.class); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0).build(); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + + mTransitionObserver.onTransitionReady(transition, info, startT, finishT); + + verify(mDesktopFullImmersiveTransitionHandler).onTransitionReady(transition, info); + } + private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) { final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = taskId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt index 0c3f98a324cd..0c100fca2036 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt @@ -30,7 +30,7 @@ import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT import com.android.wm.shell.shared.split.SplitBounds -import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50 +import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows @@ -136,7 +136,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { assertThat(recentTaskInfoParcel.taskInfo2).isNotNull() assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2) assertThat(recentTaskInfoParcel.splitBounds).isNotNull() - assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_50_50) + assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_2_50_50) } @Test @@ -185,7 +185,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { private fun splitTasksGroupInfo(): GroupedRecentTaskInfo { val task1 = createTaskInfo(id = 1) val task2 = createTaskInfo(id = 2) - val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_50_50) + val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 386253c19c82..9b73d53e0639 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -22,7 +22,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50; +import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -50,6 +51,7 @@ import android.app.KeyguardManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.platform.test.annotations.DisableFlags; @@ -68,7 +70,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.shared.GroupedRecentTaskInfo; import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -107,7 +109,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock - private DesktopModeTaskRepository mDesktopModeTaskRepository; + private DesktopRepository mDesktopRepository; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -144,7 +146,7 @@ public class RecentTasksControllerTest extends ShellTestCase { mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, - Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver, + Optional.of(mDesktopRepository), mTaskStackTransitionObserver, mMainExecutor); mRecentTasksController = spy(mRecentTasksControllerReal); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, @@ -211,10 +213,10 @@ public class RecentTasksControllerTest extends ShellTestCase { // Verify only one update if the split info is the same SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50), - new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50); + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1); SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50), - new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50); + new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2); verify(mRecentTasksController, times(1)).notifyRecentTasksChanged(); } @@ -246,9 +248,9 @@ public class RecentTasksControllerTest extends ShellTestCase { // Mark a couple pairs [t2, t4], [t3, t5] SplitBounds pair1Bounds = - new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50); + new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50); SplitBounds pair2Bounds = - new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50); + new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); @@ -277,9 +279,9 @@ public class RecentTasksControllerTest extends ShellTestCase { // Mark a couple pairs [t2, t4], [t3, t5] SplitBounds pair1Bounds = - new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50); + new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50); SplitBounds pair2Bounds = - new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50); + new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); @@ -303,8 +305,8 @@ public class RecentTasksControllerTest extends ShellTestCase { ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopRepository.isActiveTask(1)).thenReturn(true); + when(mDesktopRepository.isActiveTask(3)).thenReturn(true); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -339,11 +341,11 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5); SplitBounds pair1Bounds = - new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_50_50); + new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds); - when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true); - when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true); + when(mDesktopRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopRepository.isActiveTask(5)).thenReturn(true); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -382,8 +384,8 @@ public class RecentTasksControllerTest extends ShellTestCase { ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopRepository.isActiveTask(1)).thenReturn(true); + when(mDesktopRepository.isActiveTask(3)).thenReturn(true); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -410,10 +412,10 @@ public class RecentTasksControllerTest extends ShellTestCase { ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); - when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true); - when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true); - when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true); + when(mDesktopRepository.isActiveTask(1)).thenReturn(true); + when(mDesktopRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopRepository.isActiveTask(5)).thenReturn(true); + when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -441,6 +443,40 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) + public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() { + ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + + t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400); + t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450); + setRawList(t1, t2); + + when(mDesktopRepository.isActiveTask(1)).thenReturn(true); + when(mDesktopRepository.isActiveTask(2)).thenReturn(true); + + ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( + MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + + assertEquals(1, recentTasks.size()); + GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); + + // Check bounds + assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get( + 0).configuration.windowConfiguration.getAppBounds()); + assertEquals(t2.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get( + 1).configuration.windowConfiguration.getAppBounds()); + + // Check position in parent + assertEquals(new Point(t1.lastNonFullscreenBounds.left, + t1.lastNonFullscreenBounds.top), + freeformGroup.getTaskInfoList().get(0).positionInParent); + assertEquals(new Point(t2.lastNonFullscreenBounds.left, + t2.lastNonFullscreenBounds.top), + freeformGroup.getTaskInfoList().get(1).positionInParent); + } + + @Test public void testRemovedTaskRemovesSplit() { ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); @@ -449,7 +485,7 @@ public class RecentTasksControllerTest extends ShellTestCase { // Add a pair SplitBounds pair1Bounds = - new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_50_50); + new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds); reset(mRecentTasksController); @@ -623,6 +659,7 @@ public class RecentTasksControllerTest extends ShellTestCase { private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); info.taskId = taskId; + info.lastNonFullscreenBounds = new Rect(); return info; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java index 769acf7fdfde..0effc3e3d6b8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java @@ -48,7 +48,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -82,7 +82,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock - private DesktopModeTaskRepository mDesktopModeTaskRepository; + private DesktopRepository mDesktopRepository; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -120,7 +120,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, - Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver, + Optional.of(mDesktopRepository), mTaskStackTransitionObserver, mMainExecutor); mRecentTasksController = spy(mRecentTasksControllerReal); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java index 248393cef9ae..be8e6dc3154b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java @@ -1,6 +1,6 @@ package com.android.wm.shell.recents; -import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -46,21 +46,21 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testVerticalStacked() { SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, - TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); + TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50); assertTrue(ssb.appsStackedVertically); } @Test public void testHorizontalStacked() { SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, - TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); + TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50); assertFalse(ssb.appsStackedVertically); } @Test public void testHorizontalDividerBounds() { SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, - TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); + TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50); Rect dividerBounds = ssb.visualDividerBounds; assertEquals(0, dividerBounds.left); assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top); @@ -71,7 +71,7 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testVerticalDividerBounds() { SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, - TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); + TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50); Rect dividerBounds = ssb.visualDividerBounds; assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left); assertEquals(0, dividerBounds.top); @@ -82,7 +82,7 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testEqualVerticalTaskPercent() { SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, - TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); + TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50); float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH; assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01); } @@ -90,7 +90,7 @@ public class SplitBoundsTest extends ShellTestCase { @Test public void testEqualHorizontalTaskPercent() { SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, - TASK_ID_1, TASK_ID_2, SNAP_TO_50_50); + TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50); float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH; assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt index 0e5efa650cc4..afdb68776d04 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt @@ -114,7 +114,7 @@ class TaskStackTransitionObserverTest { @Test @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL) - fun taskCreated_fullscreenWindow_listenerNotNotified() { + fun taskCreated_fullscreenWindow_listenerNotified() { val listener = TestListener() val executor = TestShellExecutor() transitionObserver.addTaskStackTransitionObserverListener(listener, executor) @@ -130,9 +130,9 @@ class TaskStackTransitionObserverTest { callOnTransitionFinished() executor.flushAll() - assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(0) + assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(1) assertThat(listener.taskInfoToBeNotified.windowingMode) - .isEqualTo(WindowConfiguration.WINDOWING_MODE_UNDEFINED) + .isEqualTo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt index 641063c27076..205defef5dd5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt @@ -16,6 +16,8 @@ package com.android.wm.shell.shared.bubbles +import android.graphics.drawable.Icon +import android.net.Uri import android.os.Parcel import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE import android.testing.AndroidTestingRunner @@ -42,7 +44,12 @@ class BubbleInfoTest : ShellTestCase() { "title", "Some app", true, - true + true, + ParcelableFlyoutMessage( + Icon.createWithContentUri(Uri.parse("content://image/123")), + "sender", + "message" + ) ) val parcel = Parcel.obtain() bubbleInfo.writeToParcel(parcel, PARCELABLE_WRITE_RETURN_VALUE) @@ -60,5 +67,10 @@ class BubbleInfoTest : ShellTestCase() { assertThat(bubbleInfo.appName).isEqualTo(bubbleInfoFromParcel.appName) assertThat(bubbleInfo.isImportantConversation) .isEqualTo(bubbleInfoFromParcel.isImportantConversation) + with(bubbleInfo.parcelableFlyoutMessage!!) { + assertThat(icon!!.uri.toString()).isEqualTo("content://image/123") + assertThat(title).isEqualTo("sender") + assertThat(message).isEqualTo("message") + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt index 19c18be44ab1..ac9606350ebd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt @@ -42,19 +42,44 @@ class SplitScreenConstantsTest { SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT, ) assertEquals( - "the value of SNAP_TO_30_70 should be 0", + "the value of SNAP_TO_2_33_66 should be 0", 0, - SplitScreenConstants.SNAP_TO_30_70, + SplitScreenConstants.SNAP_TO_2_33_66, ) assertEquals( - "the value of SNAP_TO_50_50 should be 1", + "the value of SNAP_TO_2_50_50 should be 1", 1, - SplitScreenConstants.SNAP_TO_50_50, + SplitScreenConstants.SNAP_TO_2_50_50, ) assertEquals( - "the value of SNAP_TO_70_30 should be 2", + "the value of SNAP_TO_2_66_33 should be 2", 2, - SplitScreenConstants.SNAP_TO_70_30, + SplitScreenConstants.SNAP_TO_2_66_33, + ) + assertEquals( + "the value of SNAP_TO_2_90_10 should be 3", + 3, + SplitScreenConstants.SNAP_TO_2_90_10, + ) + assertEquals( + "the value of SNAP_TO_2_10_90 should be 4", + 4, + SplitScreenConstants.SNAP_TO_2_10_90, + ) + assertEquals( + "the value of SNAP_TO_3_33_33_33 should be 5", + 5, + SplitScreenConstants.SNAP_TO_3_33_33_33, + ) + assertEquals( + "the value of SNAP_TO_3_45_45_10 should be 6", + 6, + SplitScreenConstants.SNAP_TO_3_45_45_10, + ) + assertEquals( + "the value of SNAP_TO_3_10_45_45 should be 7", + 7, + SplitScreenConstants.SNAP_TO_3_10_45_45, ) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index 5f7542332c80..ce482cdd9944 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -30,7 +30,6 @@ import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MIN import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.times; @@ -56,12 +55,11 @@ import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.testing.TestableContext; -import android.view.IWindowSession; import android.view.InsetsState; import android.view.Surface; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.WindowMetrics; +import android.window.SnapshotDrawerUtils; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskSnapshot; @@ -220,18 +218,10 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { createWindowInfo(taskId, android.R.style.Theme, mBinder); TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100), new Rect(0, 0, 0, 50), true /* hasImeSurface */); - final IWindowSession session = WindowManagerGlobal.getWindowSession(); - spyOn(session); - doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay( - any() /* window */, any() /* attrs */, - anyInt() /* viewVisibility */, anyInt() /* displayId */, - anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */, - any() /* outInsetsState */, any() /* outActiveControls */, - any() /* outAttachedFrame */, any() /* outSizeCompatScale */); - TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo, - mBinder, - snapshot, mTestExecutor, () -> { - }); + final TaskSnapshotWindow mockSnapshotWindow = new TaskSnapshotWindow( + snapshot, SnapshotDrawerUtils.getOrCreateTaskDescription(windowInfo.taskInfo), + snapshot.getOrientation(), + () -> {}, mTestExecutor); spyOn(mockSnapshotWindow); try (AutoCloseable mockTaskSnapshotSession = new AutoCloseable() { MockitoSession mockSession = mockitoSession() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java index d37b4cf4b4b3..015ea20767e9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java @@ -18,11 +18,12 @@ package com.android.wm.shell.transition; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -30,9 +31,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.app.ActivityManager.RunningTaskInfo; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -43,17 +41,11 @@ import android.window.TransitionInfo.TransitionMode; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; import com.android.window.flags.Flags; -import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.shared.IFocusTransitionListener; -import com.android.wm.shell.shared.TransactionPool; -import com.android.wm.shell.sysui.ShellController; -import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.shared.FocusTransitionListener; import org.junit.Before; import org.junit.Rule; @@ -75,72 +67,67 @@ public class FocusTransitionObserverTest extends ShellTestCase { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private IFocusTransitionListener mListener; - private Transitions mTransition; + private FocusTransitionListener mListener; + private final TestShellExecutor mShellExecutor = new TestShellExecutor(); private FocusTransitionObserver mFocusTransitionObserver; @Before public void setUp() { - mListener = mock(IFocusTransitionListener.class); - when(mListener.asBinder()).thenReturn(mock(IBinder.class)); - + mListener = mock(FocusTransitionListener.class); mFocusTransitionObserver = new FocusTransitionObserver(); - mTransition = - new Transitions(InstrumentationRegistry.getInstrumentation().getTargetContext(), - mock(ShellInit.class), mock(ShellController.class), - mock(ShellTaskOrganizer.class), mock(TransactionPool.class), - mock(DisplayController.class), new TestShellExecutor(), - new Handler(Looper.getMainLooper()), new TestShellExecutor(), - mock(HomeTransitionObserver.class), - mFocusTransitionObserver); - mFocusTransitionObserver.setRemoteFocusTransitionListener(mTransition, mListener); + mFocusTransitionObserver.setLocalFocusTransitionListener(mListener, mShellExecutor); + mShellExecutor.flushAll(); + clearInvocations(mListener); } @Test - public void testTransitionWithMovedToFrontFlagChangesDisplayFocus() throws RemoteException { - final IBinder binder = mock(IBinder.class); + public void testBasicTaskAndDisplayFocusSwitch() throws RemoteException { final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class); - // Open a task on the default display, which doesn't change display focus because the - // default display already has it. + // First, open a task on the default display. TransitionInfo info = mock(TransitionInfo.class); final List<TransitionInfo.Change> changes = new ArrayList<>(); - setupChange(changes, 123 /* taskId */, TRANSIT_OPEN, DEFAULT_DISPLAY, - true /* focused */); + setupTaskChange(changes, 1 /* taskId */, TRANSIT_OPEN, + DEFAULT_DISPLAY, true /* focused */); when(info.getChanges()).thenReturn(changes); - mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx); - verify(mListener, never()).onFocusedDisplayChanged(SECONDARY_DISPLAY_ID); + mFocusTransitionObserver.updateFocusState(info); + mShellExecutor.flushAll(); + verify(mListener, never()).onFocusedDisplayChanged(anyInt()); + verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */, + true /* isFocusedOnDisplay */, true /* isFocusedGlobally */); clearInvocations(mListener); - // Open a new task on the secondary display and verify display focus changes to the display. - changes.clear(); - setupChange(changes, 456 /* taskId */, TRANSIT_OPEN, SECONDARY_DISPLAY_ID, - true /* focused */); + // Open a task on the secondary display. + setupTaskChange(changes, 2 /* taskId */, TRANSIT_OPEN, + SECONDARY_DISPLAY_ID, true /* focused */); + setupDisplayToTopChange(changes, SECONDARY_DISPLAY_ID); when(info.getChanges()).thenReturn(changes); - mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx); - verify(mListener, times(1)).onFocusedDisplayChanged(SECONDARY_DISPLAY_ID); + mFocusTransitionObserver.updateFocusState(info); + mShellExecutor.flushAll(); + verify(mListener, times(1)) + .onFocusedDisplayChanged(SECONDARY_DISPLAY_ID); + verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */, + true /* isFocusedOnDisplay */, false /* isFocusedGlobally */); + verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */, + true /* isFocusedOnDisplay */, true /* isFocusedGlobally */); clearInvocations(mListener); - // Open the first task to front and verify display focus goes back to the default display. + // Moving only the default display back to front, and verify that affected tasks are also + // notified. changes.clear(); - setupChange(changes, 123 /* taskId */, TRANSIT_TO_FRONT, DEFAULT_DISPLAY, - true /* focused */); + setupDisplayToTopChange(changes, DEFAULT_DISPLAY); when(info.getChanges()).thenReturn(changes); - mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx); - verify(mListener, times(1)).onFocusedDisplayChanged(DEFAULT_DISPLAY); - clearInvocations(mListener); - - // Open another task on the default display and verify no display focus switch as it's - // already on the default display. - changes.clear(); - setupChange(changes, 789 /* taskId */, TRANSIT_OPEN, DEFAULT_DISPLAY, - true /* focused */); - when(info.getChanges()).thenReturn(changes); - mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx); - verify(mListener, never()).onFocusedDisplayChanged(DEFAULT_DISPLAY); + mFocusTransitionObserver.updateFocusState(info); + mShellExecutor.flushAll(); + verify(mListener, times(1)) + .onFocusedDisplayChanged(DEFAULT_DISPLAY); + verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */, + true /* isFocusedOnDisplay */, true /* isFocusedGlobally */); + verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */, + true /* isFocusedOnDisplay */, false /* isFocusedGlobally */); } - private void setupChange(List<TransitionInfo.Change> changes, int taskId, + private void setupTaskChange(List<TransitionInfo.Change> changes, int taskId, @TransitionMode int mode, int displayId, boolean focused) { TransitionInfo.Change change = mock(TransitionInfo.Change.class); RunningTaskInfo taskInfo = mock(RunningTaskInfo.class); @@ -152,4 +139,12 @@ public class FocusTransitionObserverTest extends ShellTestCase { when(change.getMode()).thenReturn(mode); changes.add(change); } + + private void setupDisplayToTopChange(List<TransitionInfo.Change> changes, int displayId) { + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + when(change.hasFlags(FLAG_MOVED_TO_TOP)).thenReturn(true); + when(change.hasFlags(FLAG_IS_DISPLAY)).thenReturn(true); + when(change.getEndDisplayId()).thenReturn(displayId); + changes.add(change); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationProtoUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt index def4b916a5f7..708fadb7fbe2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationProtoUtils.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt @@ -16,9 +16,58 @@ package com.android.wm.shell.util +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.content.pm.ActivityInfo +import android.graphics.Rect +import com.android.wm.shell.desktopmode.CaptionState import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto /** + * Create an instance of [CaptionState.AppHandle] with parameters as properties. + * + * Any fields without corresponding parameters will retain their default values. + */ +fun createAppHandleState( + runningTaskInfo: RunningTaskInfo = createTaskInfo(), + isHandleMenuExpanded: Boolean = false, + globalAppHandleBounds: Rect = Rect(), +): CaptionState.AppHandle = + CaptionState.AppHandle( + runningTaskInfo = runningTaskInfo, + isHandleMenuExpanded = isHandleMenuExpanded, + globalAppHandleBounds = globalAppHandleBounds) + +/** + * Create an instance of [CaptionState.AppHeader] with parameters as properties. + * + * Any fields without corresponding parameters will retain their default values. + */ +fun createAppHeaderState( + runningTaskInfo: RunningTaskInfo = createTaskInfo(), + isHeaderMenuExpanded: Boolean = false, + globalAppChipBounds: Rect = Rect(), +): CaptionState.AppHeader = + CaptionState.AppHeader( + runningTaskInfo = runningTaskInfo, + isHeaderMenuExpanded = isHeaderMenuExpanded, + globalAppChipBounds = globalAppChipBounds) + +/** + * Create an instance of [RunningTaskInfo] with parameters as properties. + * + * Any fields without corresponding parameters will retain their default values. + */ +fun createTaskInfo( + deviceWindowingMode: Int = WINDOWING_MODE_UNDEFINED, + runningTaskPackageName: String = GMAIL_PACKAGE_NAME, +): RunningTaskInfo = + RunningTaskInfo().apply { + configuration.windowConfiguration.windowingMode = deviceWindowingMode + topActivityInfo = ActivityInfo().apply { packageName = runningTaskPackageName } + } + +/** * Constructs a [WindowingEducationProto] object, populating its fields with the provided * parameters. * @@ -61,3 +110,7 @@ fun createAppHandleEducationProto( } } .build() + +const val GMAIL_PACKAGE_NAME = "com.google.android.gm" +const val YOUTUBE_PACKAGE_NAME = "com.google.android.youtube" +const val LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher" diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt index d141c2d771ce..5ebf5170bf86 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt @@ -47,7 +47,10 @@ class CaptionWindowDecorationTests : ShellTestCase() { taskInfo, true, false, - InsetsState() + true /* isStatusBarVisible */, + false /* isKeyguardVisibleAndOccluded */, + InsetsState(), + true /* hasGlobalFocus */ ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue() @@ -66,7 +69,10 @@ class CaptionWindowDecorationTests : ShellTestCase() { taskInfo, true, false, - InsetsState() + true /* isStatusBarVisible */, + false /* isKeyguardVisibleAndOccluded */, + InsetsState(), + true /* hasGlobalFocus */ ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse() @@ -81,7 +87,10 @@ class CaptionWindowDecorationTests : ShellTestCase() { taskInfo, true, false, - InsetsState() + true /* isStatusBarVisible */, + false /* isKeyguardVisibleAndOccluded */, + InsetsState(), + true /* hasGlobalFocus */ ) Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2) Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 3051714b5ae8..175fbd2396e3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -27,6 +27,7 @@ import android.app.WindowConfiguration.WindowingMode import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.Intent.ACTION_MAIN import android.content.pm.ActivityInfo import android.graphics.Rect import android.hardware.display.DisplayManager @@ -64,6 +65,7 @@ import android.widget.Toast import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession @@ -85,10 +87,12 @@ import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler +import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.DesktopTasksLimiter import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository +import com.android.wm.shell.desktopmode.education.AppHandleEducationController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource @@ -96,6 +100,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener @@ -105,6 +110,7 @@ import java.util.function.Consumer import java.util.function.Supplier import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -121,7 +127,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.kotlin.verify import org.mockito.kotlin.any -import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doNothing @@ -136,6 +141,7 @@ import org.mockito.quality.Strictness * Tests of [DesktopModeWindowDecorViewModel] * Usage: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests */ +@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper @@ -155,6 +161,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockSplitScreenController: SplitScreenController + @Mock private lateinit var mockDesktopRepository: DesktopRepository @Mock private lateinit var mockDisplayLayout: DisplayLayout @Mock private lateinit var displayInsetsController: DisplayInsetsController @Mock private lateinit var mockSyncQueue: SyncTransactionQueue @@ -184,6 +191,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskPositionerFactory: DesktopModeWindowDecorViewModel.TaskPositionerFactory @Mock private lateinit var mockTaskPositioner: TaskPositioner + @Mock private lateinit var mockAppHandleEducationController: AppHandleEducationController + @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository private lateinit var spyContext: TestableContext @@ -225,6 +234,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockShellCommandHandler, mockWindowManager, mockTaskOrganizer, + mockDesktopRepository, mockDisplayController, mockShellController, displayInsetsController, @@ -242,9 +252,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { windowDecorByTaskIdSpy, mockInteractionJankMonitor, Optional.of(mockTasksLimiter), + mockAppHandleEducationController, mockCaptionHandleRepository, Optional.of(mockActivityOrientationChangeHandler), - mockTaskPositionerFactory + mockTaskPositionerFactory, + mockFocusTransitionObserver ) desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -445,24 +457,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onClickListenerCaptor.value.onClick(view) - val transactionCaptor = argumentCaptor<WindowContainerTransaction>() - verify(mockFreeformTaskTransitionStarter) - .startMinimizedModeTransition(transactionCaptor.capture()) - val wct = transactionCaptor.firstValue - - verify(mockTasksLimiter).addPendingMinimizeChange( - anyOrNull(), eq(DEFAULT_DISPLAY), eq(decor.mTaskInfo.taskId)) - - assertEquals(1, wct.getHierarchyOps().size) - assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REORDER, wct.getHierarchyOps().get(0).getType()) - assertFalse(wct.getHierarchyOps().get(0).getToTop()) - assertEquals(decor.mTaskInfo.token.asBinder(), wct.getHierarchyOps().get(0).getContainer()) + verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() { - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply { isTopActivityTransparent = true isTopActivityStyleFloating = true numActivities = 1 @@ -477,7 +478,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() { - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply { isTopActivityTransparent = true isTopActivityStyleFloating = false numActivities = 1 @@ -490,7 +491,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsNotCreatedForSystemUIActivities() { - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN) // Set task as systemUI package val systemUIPackageName = context.resources.getString( @@ -563,7 +564,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { // Simulate default enforce device restrictions system property whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN) // Simulate device that doesn't support desktop mode doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } @@ -579,7 +580,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { // Simulate device that doesn't support desktop mode doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN) setUpMockDecorationsForTasks(task) onTaskOpening(task) @@ -592,7 +593,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { // Simulate default enforce device restrictions system property whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) - val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN) doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } setUpMockDecorationsForTasks(task) @@ -924,13 +925,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test fun testDecor_onClickToOpenBrowser_closeMenus() { val openInBrowserListenerCaptor = forClass(Consumer::class.java) - as ArgumentCaptor<Consumer<Uri>> + as ArgumentCaptor<Consumer<Intent>> val decor = createOpenTaskDecoration( windowingMode = WINDOWING_MODE_FULLSCREEN, onOpenInBrowserClickListener = openInBrowserListenerCaptor ) - openInBrowserListenerCaptor.value.accept(Uri.EMPTY) + openInBrowserListenerCaptor.value.accept(Intent()) verify(decor).closeHandleMenu() verify(decor).closeMaximizeMenu() @@ -940,26 +941,102 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { fun testDecor_onClickToOpenBrowser_opensBrowser() { doNothing().whenever(spyContext).startActivity(any()) val uri = Uri.parse("https://www.google.com") + val intent = Intent(ACTION_MAIN, uri) val openInBrowserListenerCaptor = forClass(Consumer::class.java) - as ArgumentCaptor<Consumer<Uri>> + as ArgumentCaptor<Consumer<Intent>> createOpenTaskDecoration( windowingMode = WINDOWING_MODE_FULLSCREEN, onOpenInBrowserClickListener = openInBrowserListenerCaptor ) - openInBrowserListenerCaptor.value.accept(uri) + openInBrowserListenerCaptor.value.accept(intent) verify(spyContext).startActivityAsUser(argThat { intent -> - intent.data == uri - && ((intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != 0) - && intent.categories.contains(Intent.CATEGORY_LAUNCHER) - && intent.action == Intent.ACTION_MAIN + uri.equals(intent.data) + && intent.action == ACTION_MAIN }, eq(mockUserHandle)) } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun testDecor_createWindowDecoration_setsAppHandleEducationTooltipClickCallbacks() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + + shellInit.init() + + verify( + mockAppHandleEducationController, + times(1) + ).setAppHandleEducationTooltipCallbacks(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun testDecor_invokeOpenHandleMenuCallback_openHandleMenu() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) + val decor = setUpMockDecorationForTask(task) + val openHandleMenuCallbackCaptor = argumentCaptor<(Int) -> Unit>() + // Set task as gmail + val gmailPackageName = "com.google.android.gm" + val baseComponent = ComponentName(gmailPackageName, /* class */ "") + task.baseActivity = baseComponent + + onTaskOpening(task) + verify( + mockAppHandleEducationController, + times(1) + ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any()) + openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId) + + verify(decor, times(1)).createHandleMenu(anyBoolean()) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun testDecor_openTaskWithFlagDisabled_doNotOpenHandleMenu() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) + setUpMockDecorationForTask(task) + val openHandleMenuCallbackCaptor = argumentCaptor<(Int) -> Unit>() + // Set task as gmail + val gmailPackageName = "com.google.android.gm" + val baseComponent = ComponentName(gmailPackageName, /* class */ "") + task.baseActivity = baseComponent + + onTaskOpening(task) + verify( + mockAppHandleEducationController, + never() + ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun testDecor_invokeOnToDesktopCallback_setsAppHandleEducationTooltipClickCallbacks() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) + setUpMockDecorationsForTasks(task) + onTaskOpening(task) + val onToDesktopCallbackCaptor = argumentCaptor<(Int, DesktopModeTransitionSource) -> Unit>() + + verify( + mockAppHandleEducationController, + times(1) + ).setAppHandleEducationTooltipCallbacks(any(), onToDesktopCallbackCaptor.capture()) + onToDesktopCallbackCaptor.lastValue.invoke( + task.taskId, + DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON + ) + + verify(mockDesktopTasksController, times(1)) + .moveTaskToDesktop(any(), any(), any()) + } + @Test fun testOnDisplayRotation_tasksOutOfValidArea_taskBoundsUpdated() { - val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) val secondTask = createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) val thirdTask = @@ -987,7 +1064,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test fun testOnDisplayRotation_taskInValidArea_taskBoundsNotUpdated() { - val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) val secondTask = createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) val thirdTask = @@ -1014,7 +1091,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test fun testOnDisplayRotation_sameOrientationRotation_taskBoundsNotUpdated() { - val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) val secondTask = createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) val thirdTask = @@ -1038,7 +1115,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test fun testOnDisplayRotation_differentDisplayId_taskBoundsNotUpdated() { - val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FREEFORM) val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_FREEFORM) @@ -1063,7 +1140,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Test fun testOnDisplayRotation_nonFreeformTask_taskBoundsNotUpdated() { - val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FULLSCREEN) val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_PINNED) @@ -1135,9 +1212,48 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { assertEquals(decor.mTaskInfo.token.asBinder(), wct.getHierarchyOps().get(0).getContainer()) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun testMaximizeButtonClick_requestingImmersive_togglesDesktopImmersiveState() { + val onClickListenerCaptor = forClass(View.OnClickListener::class.java) + as ArgumentCaptor<View.OnClickListener> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onCaptionButtonClickListener = onClickListenerCaptor, + requestingImmersive = true, + ) + val view = mock(View::class.java) + whenever(view.id).thenReturn(R.id.maximize_window) + + onClickListenerCaptor.value.onClick(view) + + verify(mockDesktopTasksController) + .toggleDesktopTaskFullImmersiveState(decor.mTaskInfo) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun testMaximizeButtonClick_notRequestingImmersive_togglesDesktopTaskSize() { + val onClickListenerCaptor = forClass(View.OnClickListener::class.java) + as ArgumentCaptor<View.OnClickListener> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onCaptionButtonClickListener = onClickListenerCaptor, + requestingImmersive = false, + ) + val view = mock(View::class.java) + whenever(view.id).thenReturn(R.id.maximize_window) + + onClickListenerCaptor.value.onClick(view) + + verify(mockDesktopTasksController) + .toggleDesktopTaskSize(decor.mTaskInfo) + } + private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, taskSurface: SurfaceControl = SurfaceControl(), + requestingImmersive: Boolean = false, onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>, onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> = @@ -1150,14 +1266,17 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>, onToSplitScreenClickListenerCaptor: ArgumentCaptor<Function0<Unit>> = forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>, - onOpenInBrowserClickListener: ArgumentCaptor<Consumer<Uri>> = - forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Uri>>, + onOpenInBrowserClickListener: ArgumentCaptor<Consumer<Intent>> = + forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Intent>>, onCaptionButtonClickListener: ArgumentCaptor<View.OnClickListener> = forClass(View.OnClickListener::class.java) as ArgumentCaptor<View.OnClickListener>, onCaptionButtonTouchListener: ArgumentCaptor<View.OnTouchListener> = forClass(View.OnTouchListener::class.java) as ArgumentCaptor<View.OnTouchListener> ): DesktopModeWindowDecoration { - val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode)) + val decor = setUpMockDecorationForTask(createTask( + windowingMode = windowingMode, + requestingImmersive = requestingImmersive + )) onTaskOpening(decor.mTaskInfo, taskSurface) verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture()) verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture()) @@ -1194,8 +1313,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { displayId: Int = DEFAULT_DISPLAY, @WindowingMode windowingMode: Int, activityType: Int = ACTIVITY_TYPE_STANDARD, - focused: Boolean = true, activityInfo: ActivityInfo = ActivityInfo(), + requestingImmersive: Boolean = false ): RunningTaskInfo { return TestRunningTaskInfoBuilder() .setDisplayId(displayId) @@ -1204,8 +1323,12 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { .setActivityType(activityType) .build().apply { topActivityInfo = activityInfo - isFocused = focused isResizeable = true + requestedVisibleTypes = if (requestingImmersive) { + statusBars().inv() + } else { + statusBars() + } } } @@ -1213,11 +1336,10 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { val decoration = mock(DesktopModeWindowDecoration::class.java) whenever( mockDesktopModeWindowDecorFactory.create( - any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any()) + any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task - whenever(decoration.isFocused).thenReturn(task.isFocused) whenever(decoration.user).thenReturn(mockUserHandle) if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index f007115c6dab..1d11d2e8ff06 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; +import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND; @@ -37,6 +38,7 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -53,9 +55,11 @@ import android.app.ActivityManager; import android.app.assist.AssistContent; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PointF; @@ -102,6 +106,7 @@ import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -124,6 +129,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.quality.Strictness; +import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; @@ -157,6 +163,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock + private DesktopRepository mMockDesktopRepository; + @Mock private Choreographer mMockChoreographer; @Mock private SyncTransactionQueue mMockSyncQueue; @@ -187,7 +195,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private Handler mMockHandler; @Mock - private Consumer<Uri> mMockOpenInBrowserClickListener; + private Consumer<Intent> mMockOpenInBrowserClickListener; @Mock private AppToWebGenericLinksParser mMockGenericLinksParser; @Mock @@ -242,9 +250,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())) .thenReturn(false); when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel"); - final ActivityInfo activityInfo = new ActivityInfo(); - activityInfo.applicationInfo = new ApplicationInfo(); + final ActivityInfo activityInfo = createActivityInfo(); when(mMockPackageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo); + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = activityInfo; + when(mMockPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo); final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); @@ -269,7 +279,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */); // Menus should close if open before the task being invisible causes relayout to return. verify(spyWindowDecor).closeHandleMenu(); @@ -284,7 +294,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DesktopModeWindowDecoration.updateRelayoutParams( relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL); } @@ -300,7 +315,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(relayoutParams.mCornerRadius).isGreaterThan(0); } @@ -321,7 +341,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity); } @@ -343,7 +368,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity); } @@ -361,7 +391,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(relayoutParams.hasInputFeatureSpy()).isTrue(); } @@ -378,7 +413,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(relayoutParams.hasInputFeatureSpy()).isFalse(); } @@ -394,7 +434,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(relayoutParams.hasInputFeatureSpy()).isFalse(); } @@ -410,7 +455,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(hasNoInputChannelFeature(relayoutParams)).isFalse(); } @@ -427,7 +477,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue(); } @@ -444,7 +499,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue(); } @@ -462,7 +522,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue(); } @@ -481,7 +546,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue(); } @@ -498,7 +568,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat( (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) @@ -517,7 +592,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext, taskInfo, /* applyStartTransactionOnDraw= */ true, - /* shouldSetTaskPositionAndCrop */ false); + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); assertThat( (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0) @@ -525,12 +605,207 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void updateRelayoutParams_header_addsPaddingInFullImmersive() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000)); + final InsetsState insetsState = createInsetsState(List.of( + createInsetsSource( + 0 /* id */, statusBars(), true /* visible */, new Rect(0, 0, 1000, 50)), + createInsetsSource( + 1 /* id */, captionBar(), true /* visible */, new Rect(0, 0, 1000, 100)))); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ true, + insetsState, + /* hasGlobalFocus= */ true); + + // Takes status bar inset as padding, ignores caption bar inset. + assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void updateRelayoutParams_header_notAnInsetsSourceInFullyImmersive() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ true, + new InsetsState(), + /* hasGlobalFocus= */ true); + + assertThat(relayoutParams.mIsInsetSource).isFalse(); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void updateRelayoutParams_header_statusBarInvisible_captionVisible() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ false, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); + + // Header is always shown because it's assumed the status bar is always visible. + assertThat(relayoutParams.mIsCaptionVisible).isTrue(); + } + + @Test + public void updateRelayoutParams_handle_statusBarVisibleAndNotOverKeyguard_captionVisible() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); + + assertThat(relayoutParams.mIsCaptionVisible).isTrue(); + } + + @Test + public void updateRelayoutParams_handle_statusBarInvisible_captionNotVisible() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ false, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); + + assertThat(relayoutParams.mIsCaptionVisible).isFalse(); + } + + @Test + public void updateRelayoutParams_handle_overKeyguard_captionNotVisible() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ true, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true); + + assertThat(relayoutParams.mIsCaptionVisible).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void updateRelayoutParams_header_fullyImmersive_captionVisFollowsStatusBar() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ true, + new InsetsState(), + /* hasGlobalFocus= */ true); + + assertThat(relayoutParams.mIsCaptionVisible).isTrue(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ false, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ true, + new InsetsState(), + /* hasGlobalFocus= */ true); + + assertThat(relayoutParams.mIsCaptionVisible).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void updateRelayoutParams_header_fullyImmersive_overKeyguard_captionNotVisible() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ true, + /* inFullImmersiveMode */ true, + new InsetsState(), + /* hasGlobalFocus= */ true); + + assertThat(relayoutParams.mIsCaptionVisible).isFalse(); + } + + @Test public void relayout_fullscreenTask_appliesTransactionImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockTransaction).apply(); verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); @@ -545,7 +820,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT) taskInfo.isResizeable = false; - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockTransaction, never()).apply(); verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction); @@ -557,7 +832,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any()); } @@ -569,7 +844,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); // Once for view host, the other for the AppHandle input layer. verify(mMockHandler, times(2)).post(runnableArgument.capture()); @@ -586,7 +861,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT) taskInfo.isResizeable = false; - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); verify(mMockHandler, never()).post(any()); @@ -598,11 +873,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); // Once for view host, the other for the AppHandle input layer. verify(mMockHandler, times(2)).post(runnableArgument.capture()); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); } @@ -613,7 +888,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); // Once for view host, the other for the AppHandle input layer. verify(mMockHandler, times(2)).post(runnableArgument.capture()); @@ -746,7 +1021,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { runnableArgument.getValue().run(); // Relayout decor with same captured link - decor.relayout(taskInfo); + decor.relayout(taskInfo, true /* hasGlobalFocus */); // Verify handle menu's browser link not set to captured link since link is expired createHandleMenu(decor); @@ -771,7 +1046,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Verify handle menu's browser link is set to captured link since menu was opened before // captured link expired - createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI1); } @@ -782,7 +1056,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, null /* web uri */, null /* generic link */); - final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor = + final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); // Simulate menu opening and clicking open in browser button @@ -795,9 +1069,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), openInBrowserCaptor.capture(), any(), + any(), any() ); - openInBrowserCaptor.getValue().invoke(TEST_URI1); + openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1)); // Verify handle menu's browser link not set to captured link since link not valid after // open in browser clicked @@ -812,7 +1087,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, null /* web uri */, null /* generic link */); - final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor = + final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); createHandleMenu(decor); verify(mMockHandleMenu).show( @@ -823,12 +1098,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), openInBrowserCaptor.capture(), any(), + any(), any() ); - openInBrowserCaptor.getValue().invoke(TEST_URI1); + openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1)); - verify(mMockOpenInBrowserClickListener).accept(TEST_URI1); + verify(mMockOpenInBrowserClickListener).accept( + argThat(intent -> intent.getData() == TEST_URI1)); } @Test @@ -872,6 +1149,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), any(), any(), + any(), closeClickListener.capture(), any() ); @@ -892,7 +1170,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockCaptionHandleRepository, never()).notifyCaptionChanged(any()); } @@ -909,7 +1187,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged( captionStateArgumentCaptor.capture()); @@ -936,7 +1214,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockAppHeaderViewHolder, atLeastOnce()).runOnAppChipGlobalLayout( runnableArgumentCaptor.capture()); runnableArgumentCaptor.getValue().invoke(); @@ -959,7 +1237,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */); verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged( captionStateArgumentCaptor.capture()); @@ -979,7 +1257,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); createHandleMenu(spyWindowDecor); verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged( @@ -1004,7 +1282,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( CaptionState.class); - spyWindowDecor.relayout(taskInfo); + spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */); createHandleMenu(spyWindowDecor); spyWindowDecor.closeHandleMenu(); @@ -1021,8 +1299,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private void verifyHandleMenuCreated(@Nullable Uri uri) { verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(), - any(), anyBoolean(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), - anyInt(), anyInt()); + any(), anyBoolean(), anyBoolean(), anyBoolean(), + argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)), + anyInt(), anyInt(), anyInt()); } private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) { @@ -1086,9 +1365,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { boolean relayout) { final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, mContext, mMockDisplayController, mMockSplitScreenController, - mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, - mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory, - mMockRootTaskDisplayAreaOrganizer, + mMockDesktopRepository, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, + mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, + mMockAppHeaderViewHolderFactory, mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, @@ -1100,7 +1379,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener); windowDecor.mDecorWindowContext = mContext; if (relayout) { - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); } return windowDecor; } @@ -1128,19 +1407,39 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { decor.onAssistContentReceived(mAssistContent); } + private static ActivityInfo createActivityInfo() { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = "DesktopModeWindowDecorationTestPackage"; + final ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.applicationInfo = applicationInfo; + activityInfo.name = "DesktopModeWindowDecorationTest"; + return activityInfo; + } + private static boolean hasNoInputChannelFeature(RelayoutParams params) { return (params.mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) != 0; } - private InsetsState createInsetsState(@WindowInsets.Type.InsetsType int type, boolean visible) { - final InsetsState state = new InsetsState(); - final InsetsSource source = new InsetsSource(/* id= */0, type); + private InsetsSource createInsetsSource(int id, @WindowInsets.Type.InsetsType int type, + boolean visible, @NonNull Rect frame) { + final InsetsSource source = new InsetsSource(id, type); source.setVisible(visible); - state.addSource(source); + source.setFrame(frame); + return source; + } + + private InsetsState createInsetsState(@NonNull List<InsetsSource> sources) { + final InsetsState state = new InsetsState(); + sources.forEach(state::addSource); return state; } + private InsetsState createInsetsState(@WindowInsets.Type.InsetsType int type, boolean visible) { + final InsetsSource source = createInsetsSource(0 /* id */, type, visible, new Rect()); + return createInsetsState(List.of(source)); + } + private static class TestTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, View.OnGenericMotionListener, DragDetector.MotionEventHandler { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt index 7543fed4b085..ca1f9abed09e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt @@ -624,7 +624,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() { - mockWindowDecoration.mTaskInfo.isFocused = false + mockWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right STARTING_BOUNDS.left.toFloat(), @@ -640,7 +640,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() { - mockWindowDecoration.mTaskInfo.isFocused = true + mockWindowDecoration.mHasGlobalFocus = true taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right STARTING_BOUNDS.left.toFloat(), @@ -656,7 +656,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_drag_draggedTaskNotReorderedToTop() { - mockWindowDecoration.mTaskInfo.isFocused = false + mockWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag STARTING_BOUNDS.left.toFloat(), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index cabd472ec263..1820133a4795 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -240,7 +240,7 @@ class HandleMenuTest : ShellTestCase() { null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50, captionX = captionX ) - handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock()) + handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock()) return handleMenu } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 1273ee823159..1dfbd6705bf4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -323,7 +323,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread { - mockDesktopWindowDecoration.mTaskInfo.isFocused = false + mockDesktopWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right STARTING_BOUNDS.left.toFloat(), @@ -339,7 +339,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread { - mockDesktopWindowDecoration.mTaskInfo.isFocused = true + mockDesktopWindowDecoration.mHasGlobalFocus = true taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right STARTING_BOUNDS.left.toFloat(), @@ -355,7 +355,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { @Test fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread { - mockDesktopWindowDecoration.mTaskInfo.isFocused = false + mockDesktopWindowDecoration.mHasGlobalFocus = false taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag STARTING_BOUNDS.left.toFloat(), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 2e117ac9f865..bb41e9c81ece 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -47,6 +47,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -202,13 +203,12 @@ public class WindowDecorationTests extends ShellTestCase { .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) .setVisible(false) .build(); - taskInfo.isFocused = false; // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, false /* hasGlobalFocus */); verify(decorContainerSurfaceBuilder, never()).build(); verify(taskBackgroundSurfaceBuilder, never()).build(); @@ -242,12 +242,12 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); - taskInfo.isFocused = true; // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); + mRelayoutParams.mIsCaptionVisible = true; - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface); verify(decorContainerSurfaceBuilder).setContainerLayer(); @@ -314,13 +314,13 @@ public class WindowDecorationTests extends ShellTestCase { .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) .setVisible(true) .build(); - taskInfo.isFocused = true; // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); + mRelayoutParams.mIsCaptionVisible = true; - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockSurfaceControlViewHost, never()).release(); verify(t, never()).apply(); @@ -330,7 +330,7 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Transaction t2 = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t2); taskInfo.isVisible = false; - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, false /* hasGlobalFocus */); final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost); releaseOrder.verify(mMockSurfaceControlViewHost).release(); @@ -358,7 +358,7 @@ public class WindowDecorationTests extends ShellTestCase { .build(); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); // It shouldn't show the window decoration when it can't obtain the display instance. assertThat(mRelayoutResult.mRootView).isNull(); @@ -414,10 +414,9 @@ public class WindowDecorationTests extends ShellTestCase { .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) .setVisible(true) .build(); - taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class); final SurfaceControl.Builder additionalWindowSurfaceBuilder = @@ -467,11 +466,10 @@ public class WindowDecorationTests extends ShellTestCase { .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) .setVisible(true) .build(); - taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); @@ -507,11 +505,11 @@ public class WindowDecorationTests extends ShellTestCase { .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) .setVisible(true) .build(); - taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */); + windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */, + true /* hasGlobalFocus */); verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); } @@ -546,10 +544,9 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); - taskInfo.isFocused = true; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f}); @@ -571,12 +568,8 @@ public class WindowDecorationTests extends ShellTestCase { .build(); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()) - .isVisible()); - assertTrue(mInsetsState.sourceSize() == 1); - assertTrue(mInsetsState.sourceAt(0).getType() == statusBars()); - - windowDecor.relayout(taskInfo); + mRelayoutParams.mIsCaptionVisible = true; + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); @@ -612,10 +605,9 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .build(); - taskInfo.isFocused = true; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface); @@ -623,7 +615,7 @@ public class WindowDecorationTests extends ShellTestCase { } @Test - public void testRelayout_captionHidden_insetsRemoved() { + public void testRelayout_captionHidden_neverWasVisible_insetsNotRemoved() { final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController) .getDisplay(Display.DEFAULT_DISPLAY); @@ -633,24 +625,25 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .setBounds(new Rect(0, 0, 1000, 1000)) .build(); - taskInfo.isFocused = true; - // Caption visible at first. - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), true /* visible */)); + // Hidden from the beginning, so no insets were ever added. final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + mRelayoutParams.mIsCaptionVisible = false; + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); - // Hide caption so insets are removed. - windowDecor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */)); - - verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(captionBar())); - verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(mandatorySystemGestures())); + // Never added. + verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); + verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt()); + // No need to remove them if they were never added. + verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token), + any(), eq(0) /* index */, eq(captionBar())); + verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token), + any(), eq(0) /* index */, eq(mandatorySystemGestures())); } @Test - public void testRelayout_captionHidden_neverWasVisible_insetsNotRemoved() { + public void testRelayout_notAnInsetsSource_doesNotAddInsets() { final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController) .getDisplay(Display.DEFAULT_DISPLAY); @@ -660,22 +653,44 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .setBounds(new Rect(0, 0, 1000, 1000)) .build(); - // Hidden from the beginning, so no insets were ever added. - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), false /* visible */)); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo); + + mRelayoutParams.mIsInsetSource = false; + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); // Never added. verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(), eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(), eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt()); - // No need to remove them if they were never added. - verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token), - any(), eq(0) /* index */, eq(captionBar())); - verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token), - any(), eq(0) /* index */, eq(mandatorySystemGestures())); + } + + @Test + public void testRelayout_notAnInsetsSource_hadInsetsBefore_removesInsets() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setVisible(true) + .setBounds(new Rect(0, 0, 1000, 1000)) + .build(); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); + + mRelayoutParams.mIsCaptionVisible = true; + mRelayoutParams.mIsInsetSource = true; + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + + mRelayoutParams.mIsCaptionVisible = true; + mRelayoutParams.mIsInsetSource = false; + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); + + // Insets should be removed. + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar())); + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures())); } @Test @@ -692,8 +707,8 @@ public class WindowDecorationTests extends ShellTestCase { final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); // Relayout will add insets. - mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true); - windowDecor.relayout(taskInfo); + mRelayoutParams.mIsCaptionVisible = true; + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), @@ -740,15 +755,16 @@ public class WindowDecorationTests extends ShellTestCase { final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) .setVisible(true); + mRelayoutParams.mIsCaptionVisible = true; // Relayout twice with different bounds. final ActivityManager.RunningTaskInfo firstTaskInfo = builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build(); final TestWindowDecoration windowDecor = createWindowDecoration(firstTaskInfo); - windowDecor.relayout(firstTaskInfo); + windowDecor.relayout(firstTaskInfo, true /* hasGlobalFocus */); final ActivityManager.RunningTaskInfo secondTaskInfo = builder.setToken(token).setBounds(new Rect(50, 50, 1000, 1000)).build(); - windowDecor.relayout(secondTaskInfo); + windowDecor.relayout(secondTaskInfo, true /* hasGlobalFocus */); // Insets should be applied twice. verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(), @@ -767,15 +783,16 @@ public class WindowDecorationTests extends ShellTestCase { final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) .setVisible(true); + mRelayoutParams.mIsCaptionVisible = true; // Relayout twice with the same bounds. final ActivityManager.RunningTaskInfo firstTaskInfo = builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build(); final TestWindowDecoration windowDecor = createWindowDecoration(firstTaskInfo); - windowDecor.relayout(firstTaskInfo); + windowDecor.relayout(firstTaskInfo, true /* hasGlobalFocus */); final ActivityManager.RunningTaskInfo secondTaskInfo = builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build(); - windowDecor.relayout(secondTaskInfo); + windowDecor.relayout(secondTaskInfo, true /* hasGlobalFocus */); // Insets should only need to be applied once. verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(), @@ -797,9 +814,10 @@ public class WindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build(); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); + mRelayoutParams.mIsCaptionVisible = true; mRelayoutParams.mInsetSourceFlags = FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); // Caption inset source should add params' flags. verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(), @@ -820,14 +838,13 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); - taskInfo.isFocused = true; // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); mRelayoutParams.mSetTaskPositionAndCrop = false; - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockSurfaceControlStartT, never()).setWindowCrop( eq(mMockTaskSurface), anyInt(), anyInt()); @@ -850,13 +867,12 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); - taskInfo.isFocused = true; // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); mRelayoutParams.mSetTaskPositionAndCrop = true; - windowDecor.relayout(taskInfo); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); verify(mMockSurfaceControlStartT).setWindowCrop( eq(mMockTaskSurface), anyInt(), anyInt()); @@ -901,83 +917,67 @@ public class WindowDecorationTests extends ShellTestCase { } @Test - public void onStatusBarVisibilityChange_fullscreen_shownToHidden_hidesCaption() { + public void onStatusBarVisibilityChange() { final ActivityManager.RunningTaskInfo task = createTaskInfo(); task.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); when(mMockDisplayController.getInsetsState(task.displayId)) .thenReturn(createInsetsState(statusBars(), true /* visible */)); - final TestWindowDecoration decor = createWindowDecoration(task); - decor.relayout(task); - assertTrue(decor.mIsCaptionVisible); + final TestWindowDecoration decor = spy(createWindowDecoration(task)); + decor.relayout(task, true /* hasGlobalFocus */); + assertTrue(decor.mIsStatusBarVisible); decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */)); - assertFalse(decor.mIsCaptionVisible); + verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */); } @Test - public void onStatusBarVisibilityChange_fullscreen_hiddenToShown_showsCaption() { + public void onStatusBarVisibilityChange_noChange_doesNotRelayout() { final ActivityManager.RunningTaskInfo task = createTaskInfo(); task.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); when(mMockDisplayController.getInsetsState(task.displayId)) - .thenReturn(createInsetsState(statusBars(), false /* visible */)); - final TestWindowDecoration decor = createWindowDecoration(task); - decor.relayout(task); - assertFalse(decor.mIsCaptionVisible); - - decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */)); - - assertTrue(decor.mIsCaptionVisible); - } - - @Test - public void onStatusBarVisibilityChange_freeform_shownToHidden_keepsCaption() { - final ActivityManager.RunningTaskInfo task = createTaskInfo(); - task.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - when(mMockDisplayController.getInsetsState(task.displayId)) .thenReturn(createInsetsState(statusBars(), true /* visible */)); - final TestWindowDecoration decor = createWindowDecoration(task); - decor.relayout(task); - assertTrue(decor.mIsCaptionVisible); + final TestWindowDecoration decor = spy(createWindowDecoration(task)); + decor.relayout(task, true /* hasGlobalFocus */); - decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */)); + decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */)); - assertTrue(decor.mIsCaptionVisible); + verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */); } @Test - public void onKeyguardStateChange_hiddenToShownAndOccluding_hidesCaption() { + public void onKeyguardStateChange() { final ActivityManager.RunningTaskInfo task = createTaskInfo(); when(mMockDisplayController.getInsetsState(task.displayId)) .thenReturn(createInsetsState(statusBars(), true /* visible */)); - final TestWindowDecoration decor = createWindowDecoration(task); - decor.relayout(task); - assertTrue(decor.mIsCaptionVisible); + final TestWindowDecoration decor = spy(createWindowDecoration(task)); + decor.relayout(task, true /* hasGlobalFocus */); + assertFalse(decor.mIsKeyguardVisibleAndOccluded); decor.onKeyguardStateChanged(true /* visible */, true /* occluding */); - assertFalse(decor.mIsCaptionVisible); + assertTrue(decor.mIsKeyguardVisibleAndOccluded); + verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */); } @Test - public void onKeyguardStateChange_showingAndOccludingToHidden_showsCaption() { + public void onKeyguardStateChange_noChange_doesNotRelayout() { final ActivityManager.RunningTaskInfo task = createTaskInfo(); when(mMockDisplayController.getInsetsState(task.displayId)) .thenReturn(createInsetsState(statusBars(), true /* visible */)); - final TestWindowDecoration decor = createWindowDecoration(task); - decor.onKeyguardStateChanged(true /* visible */, true /* occluding */); - assertFalse(decor.mIsCaptionVisible); + final TestWindowDecoration decor = spy(createWindowDecoration(task)); + decor.relayout(task, true /* hasGlobalFocus */); + assertFalse(decor.mIsKeyguardVisibleAndOccluded); - decor.onKeyguardStateChanged(false /* visible */, false /* occluding */); + decor.onKeyguardStateChanged(false /* visible */, true /* occluding */); - assertTrue(decor.mIsCaptionVisible); + verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */); } private ActivityManager.RunningTaskInfo createTaskInfo() { final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() .setVisible(true) .build(); - taskInfo.isFocused = true; return taskInfo; } @@ -1045,8 +1045,8 @@ public class WindowDecorationTests extends ShellTestCase { } @Override - void relayout(ActivityManager.RunningTaskInfo taskInfo) { - relayout(taskInfo, false /* applyStartTransactionOnDraw */); + void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) { + relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus); } @Override @@ -1068,10 +1068,11 @@ public class WindowDecorationTests extends ShellTestCase { } void relayout(ActivityManager.RunningTaskInfo taskInfo, - boolean applyStartTransactionOnDraw) { + boolean applyStartTransactionOnDraw, boolean hasGlobalFocus) { mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; mRelayoutParams.mLayoutResId = R.layout.caption_layout; + mRelayoutParams.mHasGlobalFocus = hasGlobalFocus; relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mMockView, mRelayoutResult); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt new file mode 100644 index 000000000000..741dfb8dd885 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2024 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.windowdecor.education + +import android.annotation.LayoutRes +import android.content.Context +import android.graphics.Point +import android.testing.AndroidTestingRunner +import android.testing.TestableContext +import android.testing.TestableLooper +import android.testing.TestableResources +import android.view.MotionEvent +import android.view.Surface.ROTATION_180 +import android.view.Surface.ROTATION_90 +import android.view.View +import android.view.WindowManager +import android.widget.TextView +import android.window.WindowContainerTransaction +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.test.filters.SmallTest +import com.android.wm.shell.R +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer +import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipArrowDirection +import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner::class) +class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() { + @Mock private lateinit var mockWindowManager: WindowManager + @Mock private lateinit var mockViewContainerFactory: AdditionalSystemViewContainer.Factory + @Mock private lateinit var mockDisplayController: DisplayController + @Mock private lateinit var mockPopupWindow: AdditionalSystemViewContainer + private lateinit var testableResources: TestableResources + private lateinit var testableContext: TestableContext + private lateinit var tooltipController: DesktopWindowingEducationTooltipController + private val tooltipViewArgumentCaptor = argumentCaptor<View>() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableContext = TestableContext(mContext) + testableResources = + testableContext.orCreateTestableResources.apply { + addOverride(R.dimen.desktop_windowing_education_tooltip_padding, 10) + } + testableContext.addMockSystemService( + Context.LAYOUT_INFLATER_SERVICE, context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + testableContext.addMockSystemService(WindowManager::class.java, mockWindowManager) + tooltipController = + DesktopWindowingEducationTooltipController( + testableContext, mockViewContainerFactory, mockDisplayController) + } + + @Test + fun showEducationTooltip_createsTooltipWithCorrectText() { + val tooltipText = "This is a tooltip" + val tooltipViewConfig = createTooltipConfig(tooltipText = tooltipText) + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + + verify(mockViewContainerFactory, times(1)) + .create( + windowManagerWrapper = any(), + taskId = anyInt(), + x = anyInt(), + y = anyInt(), + width = anyInt(), + height = anyInt(), + flags = anyInt(), + view = tooltipViewArgumentCaptor.capture()) + val tooltipTextView = + tooltipViewArgumentCaptor.lastValue.findViewById<TextView>(R.id.tooltip_text) + assertThat(tooltipTextView.text).isEqualTo(tooltipText) + } + + @Test + fun showEducationTooltip_usesCorrectTaskIdForWindow() { + val tooltipViewConfig = createTooltipConfig() + val taskIdArgumentCaptor = argumentCaptor<Int>() + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + + verify(mockViewContainerFactory, times(1)) + .create( + windowManagerWrapper = any(), + taskId = taskIdArgumentCaptor.capture(), + x = anyInt(), + y = anyInt(), + width = anyInt(), + height = anyInt(), + flags = anyInt(), + view = anyOrNull()) + assertThat(taskIdArgumentCaptor.lastValue).isEqualTo(123) + } + + @Test + fun showEducationTooltip_tooltipPointsUpwards_horizontallyPositionTooltip() { + val initialTooltipX = 0 + val initialTooltipY = 0 + val tooltipViewConfig = + createTooltipConfig( + arrowDirection = TooltipArrowDirection.UP, + tooltipViewGlobalCoordinates = Point(initialTooltipX, initialTooltipY)) + val tooltipXArgumentCaptor = argumentCaptor<Int>() + val tooltipWidthArgumentCaptor = argumentCaptor<Int>() + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + + verify(mockViewContainerFactory, times(1)) + .create( + windowManagerWrapper = any(), + taskId = anyInt(), + x = tooltipXArgumentCaptor.capture(), + y = anyInt(), + width = tooltipWidthArgumentCaptor.capture(), + height = anyInt(), + flags = anyInt(), + view = tooltipViewArgumentCaptor.capture()) + val expectedTooltipX = initialTooltipX - tooltipWidthArgumentCaptor.lastValue / 2 + assertThat(tooltipXArgumentCaptor.lastValue).isEqualTo(expectedTooltipX) + } + + @Test + fun showEducationTooltip_tooltipPointsLeft_verticallyPositionTooltip() { + val initialTooltipX = 0 + val initialTooltipY = 0 + val tooltipViewConfig = + createTooltipConfig( + arrowDirection = TooltipArrowDirection.LEFT, + tooltipViewGlobalCoordinates = Point(initialTooltipX, initialTooltipY)) + val tooltipYArgumentCaptor = argumentCaptor<Int>() + val tooltipHeightArgumentCaptor = argumentCaptor<Int>() + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + + verify(mockViewContainerFactory, times(1)) + .create( + windowManagerWrapper = any(), + taskId = anyInt(), + x = anyInt(), + y = tooltipYArgumentCaptor.capture(), + width = anyInt(), + height = tooltipHeightArgumentCaptor.capture(), + flags = anyInt(), + view = tooltipViewArgumentCaptor.capture()) + val expectedTooltipY = initialTooltipY - tooltipHeightArgumentCaptor.lastValue / 2 + assertThat(tooltipYArgumentCaptor.lastValue).isEqualTo(expectedTooltipY) + } + + @Test + fun showEducationTooltip_touchEventActionOutside_dismissActionPerformed() { + val mockLambda: () -> Unit = mock() + val tooltipViewConfig = createTooltipConfig(onDismissAction = mockLambda) + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + verify(mockViewContainerFactory, times(1)) + .create( + windowManagerWrapper = any(), + taskId = anyInt(), + x = anyInt(), + y = anyInt(), + width = anyInt(), + height = anyInt(), + flags = anyInt(), + view = tooltipViewArgumentCaptor.capture()) + val motionEvent = + MotionEvent.obtain( + /* downTime= */ 0L, + /* eventTime= */ 0L, + MotionEvent.ACTION_OUTSIDE, + /* x= */ 0f, + /* y= */ 0f, + /* metaState= */ 0) + tooltipViewArgumentCaptor.lastValue.dispatchTouchEvent(motionEvent) + + verify(mockLambda).invoke() + } + + @Test + fun showEducationTooltip_tooltipClicked_onClickActionPerformed() { + val mockLambda: () -> Unit = mock() + val tooltipViewConfig = createTooltipConfig(onEducationClickAction = mockLambda) + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + verify(mockViewContainerFactory, times(1)) + .create( + windowManagerWrapper = any(), + taskId = anyInt(), + x = anyInt(), + y = anyInt(), + width = anyInt(), + height = anyInt(), + flags = anyInt(), + view = tooltipViewArgumentCaptor.capture()) + tooltipViewArgumentCaptor.lastValue.performClick() + + verify(mockLambda).invoke() + } + + @Test + fun showEducationTooltip_displayRotationChanged_hidesTooltip() { + whenever( + mockViewContainerFactory.create(any(), any(), any(), any(), any(), any(), any(), any())) + .thenReturn(mockPopupWindow) + val tooltipViewConfig = createTooltipConfig() + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + tooltipController.onDisplayChange( + /* displayId= */ 123, + /* fromRotation= */ ROTATION_90, + /* toRotation= */ ROTATION_180, + /* newDisplayAreaInfo= */ null, + WindowContainerTransaction(), + ) + + verify(mockPopupWindow, times(1)).releaseView() + verify(mockDisplayController, atLeastOnce()).removeDisplayChangingController(any()) + } + + @Test + fun showEducationTooltip_setTooltipColorScheme_correctColorsAreSet() { + val tooltipColorScheme = + TooltipColorScheme( + container = Color.Red.toArgb(), text = Color.Blue.toArgb(), icon = Color.Green.toArgb()) + val tooltipViewConfig = createTooltipConfig(tooltipColorScheme = tooltipColorScheme) + + tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) + + verify(mockViewContainerFactory, times(1)) + .create( + windowManagerWrapper = any(), + taskId = anyInt(), + x = anyInt(), + y = anyInt(), + width = anyInt(), + height = anyInt(), + flags = anyInt(), + view = tooltipViewArgumentCaptor.capture()) + val tooltipTextView = + tooltipViewArgumentCaptor.lastValue.findViewById<TextView>(R.id.tooltip_text) + assertThat(tooltipTextView.textColors.defaultColor).isEqualTo(Color.Blue.toArgb()) + } + + private fun createTooltipConfig( + @LayoutRes tooltipViewLayout: Int = R.layout.desktop_windowing_education_top_arrow_tooltip, + tooltipColorScheme: TooltipColorScheme = + TooltipColorScheme( + container = Color.Red.toArgb(), text = Color.Red.toArgb(), icon = Color.Red.toArgb()), + tooltipViewGlobalCoordinates: Point = Point(0, 0), + tooltipText: String = "This is a tooltip", + arrowDirection: TooltipArrowDirection = TooltipArrowDirection.UP, + onEducationClickAction: () -> Unit = {}, + onDismissAction: () -> Unit = {} + ) = + DesktopWindowingEducationTooltipController.EducationViewConfig( + tooltipViewLayout = tooltipViewLayout, + tooltipColorScheme = tooltipColorScheme, + tooltipViewGlobalCoordinates = tooltipViewGlobalCoordinates, + tooltipText = tooltipText, + arrowDirection = arrowDirection, + onEducationClickAction = onEducationClickAction, + onDismissAction = onDismissAction, + ) +} diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index a39f30bbad1f..385fbfe1a86a 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -75,6 +75,7 @@ cc_library { "BigBufferStream.cpp", "ChunkIterator.cpp", "ConfigDescription.cpp", + "CursorWindow.cpp", "FileStream.cpp", "Idmap.cpp", "LoadedArsc.cpp", @@ -113,7 +114,6 @@ cc_library { srcs: [ "BackupData.cpp", "BackupHelpers.cpp", - "CursorWindow.cpp", ], shared_libs: [ "libbase", @@ -147,11 +147,6 @@ cc_library { "libz", ], }, - host_linux: { - srcs: [ - "CursorWindow.cpp", - ], - }, windows: { enabled: true, }, diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 5e645cceea2d..abf2b0a91642 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -18,11 +18,12 @@ #include <androidfw/CursorWindow.h> -#include <sys/mman.h> - #include "android-base/logging.h" +#include "android-base/mapped_file.h" #include "cutils/ashmem.h" +using android::base::MappedFile; + namespace android { /** @@ -39,7 +40,7 @@ CursorWindow::CursorWindow() { CursorWindow::~CursorWindow() { if (mAshmemFd != -1) { - ::munmap(mData, mSize); + mMappedFile.reset(); ::close(mAshmemFd); } else { free(mData); @@ -75,6 +76,7 @@ fail_silent: status_t CursorWindow::maybeInflate() { int ashmemFd = 0; void* newData = nullptr; + std::unique_ptr<MappedFile> mappedFile; // Bail early when we can't expand any further if (mReadOnly || mSize == mInflatedSize) { @@ -95,11 +97,12 @@ status_t CursorWindow::maybeInflate() { goto fail_silent; } - newData = ::mmap(nullptr, mInflatedSize, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0); - if (newData == MAP_FAILED) { + mappedFile = MappedFile::FromFd(ashmemFd, 0, mInflatedSize, PROT_READ | PROT_WRITE); + if (mappedFile == nullptr) { PLOG(ERROR) << "Failed mmap"; goto fail_silent; } + newData = mappedFile->data(); if (ashmem_set_prot_region(ashmemFd, PROT_READ) < 0) { PLOG(ERROR) << "Failed ashmem_set_prot_region"; @@ -120,6 +123,7 @@ status_t CursorWindow::maybeInflate() { mData = newData; mSize = mInflatedSize; mSlotsOffset = newSlotsOffset; + mMappedFile = std::move(mappedFile); updateSlotsData(); } @@ -130,11 +134,12 @@ status_t CursorWindow::maybeInflate() { fail: LOG(ERROR) << "Failed maybeInflate"; fail_silent: - ::munmap(newData, mInflatedSize); + mappedFile.reset(); ::close(ashmemFd); return UNKNOWN_ERROR; } +#ifdef __linux__ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) { *outWindow = nullptr; @@ -167,11 +172,12 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow goto fail_silent; } - window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0); - if (window->mData == MAP_FAILED) { + window->mMappedFile = MappedFile::FromFd(window->mAshmemFd, 0, window->mSize, PROT_READ); + if (window->mMappedFile == nullptr) { PLOG(ERROR) << "Failed mmap"; goto fail_silent; } + window->mData = window->mMappedFile->data(); } else { window->mAshmemFd = -1; @@ -235,6 +241,7 @@ fail: fail_silent: return UNKNOWN_ERROR; } +#endif status_t CursorWindow::clear() { if (mReadOnly) { diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index f066e4620675..3ecd82b074a1 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -65,13 +65,7 @@ struct Idmap_data_header { uint32_t string_pool_index_offset; }; -struct Idmap_target_entry { - uint32_t target_id; - uint32_t overlay_id; -}; - struct Idmap_target_entry_inline { - uint32_t target_id; uint32_t start_value_index; uint32_t value_count; }; @@ -81,10 +75,9 @@ struct Idmap_target_entry_inline_value { Res_value value; }; -struct Idmap_overlay_entry { - uint32_t overlay_id; - uint32_t target_id; -}; +static constexpr uint32_t convert_dev_target_id(uint32_t dev_target_id) { + return (0x00FFFFFFU & dtohl(dev_target_id)); +} OverlayStringPool::OverlayStringPool(const LoadedIdmap* loaded_idmap) : data_header_(loaded_idmap->data_header_), @@ -117,27 +110,29 @@ size_t OverlayStringPool::size() const { } OverlayDynamicRefTable::OverlayDynamicRefTable(const Idmap_data_header* data_header, - const Idmap_overlay_entry* entries, + Idmap_overlay_entries entries, uint8_t target_assigned_package_id) : data_header_(data_header), entries_(entries), - target_assigned_package_id_(target_assigned_package_id) {} + target_assigned_package_id_(target_assigned_package_id) { +} status_t OverlayDynamicRefTable::lookupResourceId(uint32_t* resId) const { - const Idmap_overlay_entry* first_entry = entries_; - const Idmap_overlay_entry* end_entry = entries_ + dtohl(data_header_->overlay_entry_count); - auto entry = std::lower_bound(first_entry, end_entry, *resId, - [](const Idmap_overlay_entry& e1, const uint32_t overlay_id) { - return dtohl(e1.overlay_id) < overlay_id; - }); - - if (entry == end_entry || dtohl(entry->overlay_id) != *resId) { + const auto count = dtohl(data_header_->overlay_entry_count); + const auto overlay_it_end = entries_.overlay_id + count; + const auto entry_it = std::lower_bound(entries_.overlay_id, overlay_it_end, *resId, + [](uint32_t dev_overlay_id, uint32_t overlay_id) { + return dtohl(dev_overlay_id) < overlay_id; + }); + + if (entry_it == overlay_it_end || dtohl(*entry_it) != *resId) { // A mapping for the target resource id could not be found. return DynamicRefTable::lookupResourceId(resId); } - *resId = (0x00FFFFFFU & dtohl(entry->target_id)) - | (((uint32_t) target_assigned_package_id_) << 24U); + const auto index = entry_it - entries_.overlay_id; + *resId = convert_dev_target_id(entries_.target_id[index]) | + (((uint32_t)target_assigned_package_id_) << 24U); return NO_ERROR; } @@ -145,12 +140,10 @@ status_t OverlayDynamicRefTable::lookupResourceIdNoRewrite(uint32_t* resId) cons return DynamicRefTable::lookupResourceId(resId); } -IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, - const Idmap_target_entry* entries, - const Idmap_target_entry_inline* inline_entries, +IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, Idmap_target_entries entries, + Idmap_target_inline_entries inline_entries, const Idmap_target_entry_inline_value* inline_entry_values, - const ConfigDescription* configs, - uint8_t target_assigned_package_id, + const ConfigDescription* configs, uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) : data_header_(data_header), entries_(entries), @@ -158,7 +151,8 @@ IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, inline_entry_values_(inline_entry_values), configurations_(configs), target_assigned_package_id_(target_assigned_package_id), - overlay_ref_table_(overlay_ref_table) { } + overlay_ref_table_(overlay_ref_table) { +} IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { if ((target_res_id >> 24U) != target_assigned_package_id_) { @@ -171,15 +165,15 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { target_res_id &= 0x00FFFFFFU; // Check if the target resource is mapped to an overlay resource. - auto first_entry = entries_; - auto end_entry = entries_ + dtohl(data_header_->target_entry_count); - auto entry = std::lower_bound(first_entry, end_entry, target_res_id, - [](const Idmap_target_entry& e, const uint32_t target_id) { - return (0x00FFFFFFU & dtohl(e.target_id)) < target_id; - }); - - if (entry != end_entry && (0x00FFFFFFU & dtohl(entry->target_id)) == target_res_id) { - uint32_t overlay_resource_id = dtohl(entry->overlay_id); + const auto target_end = entries_.target_id + dtohl(data_header_->target_entry_count); + auto target_it = std::lower_bound(entries_.target_id, target_end, target_res_id, + [](uint32_t dev_target_id, uint32_t target_id) { + return convert_dev_target_id(dev_target_id) < target_id; + }); + + if (target_it != target_end && convert_dev_target_id(*target_it) == target_res_id) { + const auto index = target_it - entries_.target_id; + uint32_t overlay_resource_id = dtohl(entries_.overlay_id[index]); // Lookup the resource without rewriting the overlay resource id back to the target resource id // being looked up. overlay_ref_table_->lookupResourceIdNoRewrite(&overlay_resource_id); @@ -187,20 +181,22 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { } // Check if the target resources is mapped to an inline table entry. - auto first_inline_entry = inline_entries_; - auto end_inline_entry = inline_entries_ + dtohl(data_header_->target_inline_entry_count); - auto inline_entry = std::lower_bound(first_inline_entry, end_inline_entry, target_res_id, - [](const Idmap_target_entry_inline& e, - const uint32_t target_id) { - return (0x00FFFFFFU & dtohl(e.target_id)) < target_id; - }); - - if (inline_entry != end_inline_entry && - (0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) { + const auto inline_entry_target_end = + inline_entries_.target_id + dtohl(data_header_->target_inline_entry_count); + const auto inline_entry_target_it = + std::lower_bound(inline_entries_.target_id, inline_entry_target_end, target_res_id, + [](uint32_t dev_target_id, uint32_t target_id) { + return convert_dev_target_id(dev_target_id) < target_id; + }); + + if (inline_entry_target_it != inline_entry_target_end && + convert_dev_target_id(*inline_entry_target_it) == target_res_id) { + const auto index = inline_entry_target_it - inline_entries_.target_id; std::map<ConfigDescription, Res_value> values_map; - for (int i = 0; i < inline_entry->value_count; i++) { - const auto& value = inline_entry_values_[inline_entry->start_value_index + i]; - const auto& config = configurations_[value.config_index]; + const auto& inline_entry = inline_entries_.entry[index]; + for (int i = 0; i < dtohl(inline_entry.value_count); i++) { + const auto& value = inline_entry_values_[dtohl(inline_entry.start_value_index) + i]; + const auto& config = configurations_[dtohl(value.config_index)]; values_map[config] = value.value; } return Result(std::move(values_map)); @@ -210,15 +206,15 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { namespace { template <typename T> -const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const std::string& label, +const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const char* label, size_t count = 1) { if (!util::IsFourByteAligned(*in_out_data_ptr)) { - LOG(ERROR) << "Idmap " << label << " is not word aligned."; + LOG(ERROR) << "Idmap " << label << " in " << __func__ << " is not word aligned."; return {}; } if ((*in_out_size / sizeof(T)) < count) { - LOG(ERROR) << "Idmap too small for the number of " << label << " entries (" - << count << ")."; + LOG(ERROR) << "Idmap too small for the number of " << label << " in " << __func__ + << " entries (" << count << ")."; return nullptr; } auto data_ptr = *in_out_data_ptr; @@ -229,8 +225,8 @@ const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const st } std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size_t* in_out_size, - const std::string& label) { - const auto* len = ReadType<uint32_t>(in_out_data_ptr, in_out_size, label + " length"); + const char* label) { + const auto* len = ReadType<uint32_t>(in_out_data_ptr, in_out_size, label); if (len == nullptr) { return {}; } @@ -242,7 +238,7 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size const uint32_t padding_size = (4U - ((size_t)*in_out_data_ptr & 0x3U)) % 4U; for (uint32_t i = 0; i < padding_size; i++) { if (**in_out_data_ptr != 0) { - LOG(ERROR) << " Idmap padding of " << label << " is non-zero."; + LOG(ERROR) << " Idmap padding of " << label << " in " << __func__ << " is non-zero."; return {}; } *in_out_data_ptr += sizeof(uint8_t); @@ -258,12 +254,10 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size #endif LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* header, - const Idmap_data_header* data_header, - const Idmap_target_entry* target_entries, - const Idmap_target_entry_inline* target_inline_entries, + const Idmap_data_header* data_header, Idmap_target_entries target_entries, + Idmap_target_inline_entries target_inline_entries, const Idmap_target_entry_inline_value* inline_entry_values, - const ConfigDescription* configs, - const Idmap_overlay_entry* overlay_entries, + const ConfigDescription* configs, Idmap_overlay_entries overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, std::string_view overlay_apk_path, std::string_view target_apk_path) : header_(header), @@ -274,10 +268,12 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head configurations_(configs), overlay_entries_(overlay_entries), string_pool_(std::move(string_pool)), - idmap_fd_(android::base::utf8::open(idmap_path.c_str(), O_RDONLY|O_CLOEXEC|O_BINARY|O_PATH)), + idmap_fd_( + android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)), overlay_apk_path_(overlay_apk_path), target_apk_path_(target_apk_path), - idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {} + idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) { +} std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) { ATRACE_CALL(); @@ -319,14 +315,21 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie if (data_header == nullptr) { return {}; } - auto target_entries = ReadType<Idmap_target_entry>(&data_ptr, &data_size, "target", - dtohl(data_header->target_entry_count)); - if (target_entries == nullptr) { + Idmap_target_entries target_entries{ + .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "entries.target_id", + dtohl(data_header->target_entry_count)), + .overlay_id = ReadType<uint32_t>(&data_ptr, &data_size, "entries.overlay_id", + dtohl(data_header->target_entry_count)), + }; + if (!target_entries.target_id || !target_entries.overlay_id) { return {}; } - auto target_inline_entries = ReadType<Idmap_target_entry_inline>( - &data_ptr, &data_size, "target inline", dtohl(data_header->target_inline_entry_count)); - if (target_inline_entries == nullptr) { + Idmap_target_inline_entries target_inline_entries{ + .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "target inline.target_id", + dtohl(data_header->target_inline_entry_count)), + .entry = ReadType<Idmap_target_entry_inline>(&data_ptr, &data_size, "target inline.entry", + dtohl(data_header->target_inline_entry_count))}; + if (!target_inline_entries.target_id || !target_inline_entries.entry) { return {}; } @@ -344,9 +347,13 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie return {}; } - auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline", - dtohl(data_header->overlay_entry_count)); - if (overlay_entries == nullptr) { + Idmap_overlay_entries overlay_entries{ + .overlay_id = ReadType<uint32_t>(&data_ptr, &data_size, "overlay entries.overlay_id", + dtohl(data_header->overlay_entry_count)), + .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "overlay entries.target_id", + dtohl(data_header->overlay_entry_count)), + }; + if (!overlay_entries.overlay_id || !overlay_entries.target_id) { return {}; } std::optional<std::string_view> string_pool = ReadString(&data_ptr, &data_size, "string pool"); diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h index 9ec026a19c4c..0996355cd2c4 100644 --- a/libs/androidfw/include/androidfw/CursorWindow.h +++ b/libs/androidfw/include/androidfw/CursorWindow.h @@ -23,9 +23,13 @@ #include <string> #include "android-base/stringprintf.h" +#ifdef __linux__ #include "binder/Parcel.h" +#endif #include "utils/String8.h" +#include "android-base/mapped_file.h" + #define LOG_WINDOW(...) namespace android { @@ -80,9 +84,11 @@ public: ~CursorWindow(); static status_t create(const String8& name, size_t size, CursorWindow** outCursorWindow); +#ifdef __linux__ static status_t createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow); status_t writeToParcel(Parcel* parcel); +#endif inline String8 name() { return mName; } inline size_t size() { return mSize; } @@ -149,6 +155,8 @@ private: String8 mName; int mAshmemFd = -1; void* mData = nullptr; + std::unique_ptr<android::base::MappedFile> mMappedFile; + /** * Pointer to the first FieldSlot, used to optimize the extremely * hot code path of getFieldSlot(). diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index 64b1f0c6ed03..e213fbd22ab0 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -40,6 +40,19 @@ struct Idmap_target_entry_inline; struct Idmap_target_entry_inline_value; struct Idmap_overlay_entry; +struct Idmap_target_entries { + const uint32_t* target_id = nullptr; + const uint32_t* overlay_id = nullptr; +}; +struct Idmap_target_inline_entries { + const uint32_t* target_id = nullptr; + const Idmap_target_entry_inline* entry = nullptr; +}; +struct Idmap_overlay_entries { + const uint32_t* overlay_id = nullptr; + const uint32_t* target_id = nullptr; +}; + // A string pool for overlay apk assets. The string pool holds the strings of the overlay resources // table and additionally allows for loading strings from the idmap string pool. The idmap string // pool strings are offset after the end of the overlay resource table string pool entries so @@ -67,7 +80,7 @@ class OverlayDynamicRefTable : public DynamicRefTable { private: explicit OverlayDynamicRefTable(const Idmap_data_header* data_header, - const Idmap_overlay_entry* entries, + Idmap_overlay_entries entries, uint8_t target_assigned_package_id); // Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target @@ -75,8 +88,8 @@ class OverlayDynamicRefTable : public DynamicRefTable { status_t lookupResourceIdNoRewrite(uint32_t* resId) const; const Idmap_data_header* data_header_; - const Idmap_overlay_entry* entries_; - const int8_t target_assigned_package_id_; + Idmap_overlay_entries entries_; + uint8_t target_assigned_package_id_; friend LoadedIdmap; friend IdmapResMap; @@ -131,17 +144,15 @@ class IdmapResMap { } private: - explicit IdmapResMap(const Idmap_data_header* data_header, - const Idmap_target_entry* entries, - const Idmap_target_entry_inline* inline_entries, + explicit IdmapResMap(const Idmap_data_header* data_header, Idmap_target_entries entries, + Idmap_target_inline_entries inline_entries, const Idmap_target_entry_inline_value* inline_entry_values, - const ConfigDescription* configs, - uint8_t target_assigned_package_id, + const ConfigDescription* configs, uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table); const Idmap_data_header* data_header_; - const Idmap_target_entry* entries_; - const Idmap_target_entry_inline* inline_entries_; + Idmap_target_entries entries_; + Idmap_target_inline_entries inline_entries_; const Idmap_target_entry_inline_value* inline_entry_values_; const ConfigDescription* configurations_; const uint8_t target_assigned_package_id_; @@ -192,11 +203,11 @@ class LoadedIdmap { const Idmap_header* header_; const Idmap_data_header* data_header_; - const Idmap_target_entry* target_entries_; - const Idmap_target_entry_inline* target_inline_entries_; + Idmap_target_entries target_entries_; + Idmap_target_inline_entries target_inline_entries_; const Idmap_target_entry_inline_value* inline_entry_values_; const ConfigDescription* configurations_; - const Idmap_overlay_entry* overlay_entries_; + const Idmap_overlay_entries overlay_entries_; const std::unique_ptr<ResStringPool> string_pool_; android::base::unique_fd idmap_fd_; @@ -207,17 +218,13 @@ class LoadedIdmap { private: DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); - explicit LoadedIdmap(const std::string& idmap_path, - const Idmap_header* header, - const Idmap_data_header* data_header, - const Idmap_target_entry* target_entries, - const Idmap_target_entry_inline* target_inline_entries, + explicit LoadedIdmap(const std::string& idmap_path, const Idmap_header* header, + const Idmap_data_header* data_header, Idmap_target_entries target_entries, + Idmap_target_inline_entries target_inline_entries, const Idmap_target_entry_inline_value* inline_entry_values_, - const ConfigDescription* configs, - const Idmap_overlay_entry* overlay_entries, + const ConfigDescription* configs, Idmap_overlay_entries overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, - std::string_view overlay_apk_path, - std::string_view target_apk_path); + std::string_view overlay_apk_path, std::string_view target_apk_path); friend OverlayStringPool; }; diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index c2648909386c..e330410ed1a0 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -48,7 +48,7 @@ namespace android { constexpr const uint32_t kIdmapMagic = 0x504D4449u; -constexpr const uint32_t kIdmapCurrentVersion = 0x00000009u; +constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au; // This must never change. constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian) diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap Binary files differindex 8e847e81aa31..7e4b261cf109 100644 --- a/libs/androidfw/tests/data/overlay/overlay.idmap +++ b/libs/androidfw/tests/data/overlay/overlay.idmap diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index 3ed33db2222e..e9845c1d9f13 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -5,13 +5,19 @@ package com.google.android.appfunctions.sidecar { ctor public AppFunctionManager(android.content.Context); method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); method @Deprecated public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); + method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); + field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 + field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2 + field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1 } public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); - method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -40,11 +46,12 @@ package com.google.android.appfunctions.sidecar { method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); field public static final String PROPERTY_RETURN_VALUE = "returnValue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 + field public static final int RESULT_CANCELLED = 6; // 0x6 field public static final int RESULT_DENIED = 1; // 0x1 + field public static final int RESULT_DISABLED = 5; // 0x5 field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_TIMED_OUT = 5; // 0x5 } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java index 815fe05cc3ab..d660926575d1 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -16,11 +16,18 @@ package com.google.android.appfunctions.sidecar; +import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.UserHandleAware; import android.content.Context; import android.os.CancellationSignal; +import android.os.OutcomeReceiver; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -37,6 +44,39 @@ import java.util.function.Consumer; // TODO(b/357551503): Implement get and set enabled app function APIs. // TODO(b/367329899): Add sidecar library to Android B builds. public final class AppFunctionManager { + /** + * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset + * enabled state to the default value. + */ + public static final int APP_FUNCTION_STATE_DEFAULT = 0; + + /** + * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled} + * with this value. + */ + public static final int APP_FUNCTION_STATE_ENABLED = 1; + + /** + * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled} + * with this value. + */ + public static final int APP_FUNCTION_STATE_DISABLED = 2; + + /** + * The enabled state of the app function. + * + * @hide + */ + @IntDef( + prefix = {"APP_FUNCTION_STATE_"}, + value = { + APP_FUNCTION_STATE_DEFAULT, + APP_FUNCTION_STATE_ENABLED, + APP_FUNCTION_STATE_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EnabledState {} + private final android.app.appfunctions.AppFunctionManager mManager; private final Context mContext; @@ -111,4 +151,64 @@ public final class AppFunctionManager { new CancellationSignal(), callback); } + + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + * <p>* This method can only check app functions owned by the caller, or those where the caller + * has visibility to the owner package and holds either the {@link + * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link + * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. + * + * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: + * + * <ul> + * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not + * have access to it. + * </ul> + * + * @param functionIdentifier the identifier of the app function to check (unique within the + * target package) and in most cases, these are automatically generated by the AppFunctions + * SDK + * @param targetPackage the package name of the app function's owner + * @param executor the executor to run the request + * @param callback the callback to receive the function enabled check result + */ + public void isAppFunctionEnabled( + @NonNull String functionIdentifier, + @NonNull String targetPackage, + @NonNull Executor executor, + @NonNull OutcomeReceiver<Boolean, Exception> callback) { + mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback); + } + + /** + * Sets the enabled state of the app function owned by the calling package. + * + * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: + * + * <ul> + * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not + * have access to it. + * </ul> + * + * @param functionIdentifier the identifier of the app function to enable (unique within the + * calling package). In most cases, identifiers are automatically generated by the + * AppFunctions SDK + * @param newEnabledState the new state of the app function + * @param executor the executor to run the callback + * @param callback the callback to receive the result of the function enablement. The call was + * successful if no exception was thrown. + */ + // Constants in @EnabledState should always mirror those in + // android.app.appfunctions.AppFunctionManager. + @SuppressLint("WrongConstant") + @UserHandleAware + public void setAppFunctionEnabled( + @NonNull String functionIdentifier, + @EnabledState int newEnabledState, + @NonNull Executor executor, + @NonNull OutcomeReceiver<Void, Exception> callback) { + mManager.setAppFunctionEnabled(functionIdentifier, newEnabledState, executor, callback); + } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java index 6023c977bd76..2a168e871713 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -24,8 +24,9 @@ import android.annotation.Nullable; import android.app.Service; import android.content.Intent; import android.os.Binder; -import android.os.IBinder; import android.os.CancellationSignal; +import android.os.IBinder; +import android.util.Log; import java.util.function.Consumer; @@ -70,18 +71,21 @@ public abstract class AppFunctionService extends Service { private final Binder mBinder = android.app.appfunctions.AppFunctionService.createBinder( /* context= */ this, - /* onExecuteFunction= */ (platformRequest, cancellationSignal, callback) -> { + /* onExecuteFunction= */ (platformRequest, + callingPackage, + cancellationSignal, + callback) -> { AppFunctionService.this.onExecuteFunction( SidecarConverter.getSidecarExecuteAppFunctionRequest( platformRequest), + callingPackage, cancellationSignal, (sidecarResponse) -> { callback.accept( SidecarConverter.getPlatformExecuteAppFunctionResponse( sidecarResponse)); }); - } - ); + }); @NonNull @Override @@ -106,11 +110,49 @@ public abstract class AppFunctionService extends Service { * thread and dispatch the result with the given callback. You should always report back the * result using the callback, no matter if the execution was successful or not. * + * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel + * the execution of function if requested by the system. + * + * @param request The function execution request. + * @param callingPackage The package name of the app that is requesting the execution. + * @param cancellationSignal A signal to cancel the execution. + * @param callback A callback to report back the result. + */ + @MainThread + public void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull String callingPackage, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + onExecuteFunction(request, cancellationSignal, callback); + } + + /** + * Called by the system to execute a specific app function. + * + * <p>This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + * <p>To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * * @param request The function execution request. * @param cancellationSignal A {@link CancellationSignal} to cancel the request. * @param callback A callback to report back the result. + * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, + * CancellationSignal, Consumer)} instead. This method will be removed once usage references + * are updated. */ @MainThread + @Deprecated public void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull CancellationSignal cancellationSignal, @@ -137,13 +179,16 @@ public abstract class AppFunctionService extends Service { * * @param request The function execution request. * @param callback A callback to report back the result. - * * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, * Consumer)} instead. This method will be removed once usage references are updated. */ @MainThread @Deprecated - public abstract void onExecuteFunction( + public void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer<ExecuteAppFunctionResponse> callback); + @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + Log.w( + "AppFunctionService", + "Calling deprecated default implementation of onExecuteFunction"); + } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index 60c25fae58d1..969e5d58b909 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -73,8 +73,14 @@ public final class ExecuteAppFunctionResponse { */ public static final int RESULT_INVALID_ARGUMENT = 4; - /** The operation was timed out. */ - public static final int RESULT_TIMED_OUT = 5; + /** The caller tried to execute a disabled app function. */ + public static final int RESULT_DISABLED = 5; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + */ + public static final int RESULT_CANCELLED = 6; /** The result code of the app function execution. */ @ResultCode private final int mResultCode; @@ -228,12 +234,13 @@ public final class ExecuteAppFunctionResponse { @IntDef( prefix = {"RESULT_"}, value = { - RESULT_OK, - RESULT_DENIED, - RESULT_APP_UNKNOWN_ERROR, - RESULT_INTERNAL_ERROR, - RESULT_INVALID_ARGUMENT, - RESULT_TIMED_OUT, + RESULT_OK, + RESULT_DENIED, + RESULT_APP_UNKNOWN_ERROR, + RESULT_INTERNAL_ERROR, + RESULT_INVALID_ARGUMENT, + RESULT_DISABLED, + RESULT_CANCELLED }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp index 27add3542c01..430519606d9b 100644 --- a/libs/hwui/AutoBackendTextureRelease.cpp +++ b/libs/hwui/AutoBackendTextureRelease.cpp @@ -141,6 +141,13 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context) return; } + if (!RenderThread::isCurrent()) { + // releaseQueueOwnership needs to run on RenderThread to prevent multithread calling + // setBackendTextureState will operate skia resource cache which need single owner + RenderThread::getInstance().queue().post([this, context]() { releaseQueueOwnership(context); }); + return; + } + LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan); if (mBackendTexture.isValid()) { // Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout. diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index b763a96e8e8a..c0160705fd47 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -19,6 +19,7 @@ #include <GLES2/gl2ext.h> // TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead. +#include <statslog_hwui.h> #include <surfacetexture/surface_texture_platform.h> #include "AutoBackendTextureRelease.h" @@ -50,6 +51,14 @@ DeferredLayerUpdater::~DeferredLayerUpdater() { setTransform(nullptr); mRenderState.removeContextCallback(this); destroyLayer(); + if (mFirstTimeForDataspace > std::chrono::steady_clock::time_point::min()) { + auto currentTime = std::chrono::steady_clock::now(); + stats_write(stats::TEXTURE_VIEW_EVENT, static_cast<int32_t>(getuid()), + static_cast<int64_t>(std::chrono::duration_cast<std::chrono::milliseconds>( + currentTime - mFirstTimeForDataspace) + .count()), + mDataspace); + } } void DeferredLayerUpdater::setSurfaceTexture(AutoTextureRelease&& consumer) { @@ -195,6 +204,21 @@ void DeferredLayerUpdater::apply() { updateLayer(forceFilter, layerImage, outTransform, currentCropRect, maxLuminanceNits); } + + if (dataspace != mDataspace || + mFirstTimeForDataspace == std::chrono::steady_clock::time_point::min()) { + auto currentTime = std::chrono::steady_clock::now(); + if (mFirstTimeForDataspace > std::chrono::steady_clock::time_point::min()) { + stats_write(stats::TEXTURE_VIEW_EVENT, static_cast<int32_t>(getuid()), + static_cast<int64_t>( + std::chrono::duration_cast<std::chrono::milliseconds>( + currentTime - mFirstTimeForDataspace) + .count()), + mDataspace); + } + mFirstTimeForDataspace = currentTime; + mDataspace = dataspace; + } } } diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index a7f8f6189a8e..3abb47ca92d1 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -16,6 +16,8 @@ #pragma once +#include <EGL/egl.h> +#include <EGL/eglext.h> #include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkImage.h> @@ -24,9 +26,9 @@ #include <android/surface_texture.h> #include <cutils/compiler.h> #include <utils/Errors.h> +#include <utils/Timers.h> -#include <EGL/egl.h> -#include <EGL/eglext.h> +#include <chrono> #include <map> #include <memory> @@ -154,6 +156,9 @@ private: bool mGLContextAttached; bool mUpdateTexImage; int mCurrentSlot = -1; + android_dataspace mDataspace = HAL_DATASPACE_UNKNOWN; + std::chrono::steady_clock::time_point mFirstTimeForDataspace = + std::chrono::steady_clock::time_point::min(); Layer* mLayer; }; diff --git a/libs/hwui/Gainmap.cpp b/libs/hwui/Gainmap.cpp index 30f401ef5f01..ea955e25dc2f 100644 --- a/libs/hwui/Gainmap.cpp +++ b/libs/hwui/Gainmap.cpp @@ -15,12 +15,37 @@ */ #include "Gainmap.h" +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkColorFilter.h> +#include <SkImagePriv.h> +#include <SkPaint.h> + +#include "HardwareBitmapUploader.h" + namespace android::uirenderer { sp<Gainmap> Gainmap::allocateHardwareGainmap(const sp<Gainmap>& srcGainmap) { auto gainmap = sp<Gainmap>::make(); gainmap->info = srcGainmap->info; - const SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap(); + SkBitmap skSrcBitmap = srcGainmap->bitmap->getSkBitmap(); + if (skSrcBitmap.info().colorType() == kAlpha_8_SkColorType && + !HardwareBitmapUploader::hasAlpha8Support()) { + // The regular Bitmap::allocateHardwareBitmap will do a conversion that preserves channels, + // so alpha8 maps to the alpha channel of rgba. However, for gainmaps we will interpret + // the data of an rgba buffer differently as we'll only look at the rgb channels + // So we need to map alpha8 to rgbx_8888 essentially + SkBitmap bitmap; + bitmap.allocPixels(skSrcBitmap.info().makeColorType(kN32_SkColorType)); + SkCanvas canvas(bitmap); + SkPaint paint; + const float alphaToOpaque[] = {0, 0, 0, 1, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 255}; + paint.setColorFilter(SkColorFilters::Matrix(alphaToOpaque, SkColorFilters::Clamp::kNo)); + canvas.drawImage(SkMakeImageFromRasterBitmap(skSrcBitmap, kNever_SkCopyPixelsMode), 0, 0, + SkSamplingOptions{}, &paint); + skSrcBitmap = bitmap; + } sk_sp<Bitmap> skBitmap(Bitmap::allocateHardwareBitmap(skSrcBitmap)); if (!skBitmap.get()) { return nullptr; diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 9e825fb350d6..7c150862a422 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -619,8 +619,11 @@ bool SkiaCanvas::useGainmapShader(Bitmap& bitmap) { // If it's an unknown colortype then it's not a bitmap-backed canvas if (info.colorType() == SkColorType::kUnknown_SkColorType) return true; + auto colorSpace = info.colorSpace(); + // If we don't have a colorspace, we can't apply a gainmap + if (!colorSpace) return false; skcms_TransferFunction tfn; - info.colorSpace()->transferFn(&tfn); + colorSpace->transferFn(&tfn); auto transferType = skcms_TransferFunction_getType(&tfn); switch (transferType) { diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index 588463c49497..e074a27db38f 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -501,18 +501,13 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) { ATRACE_CALL(); SkGainmapInfo gainmapInfo; - std::unique_ptr<SkStream> gainmapStream; + std::unique_ptr<SkAndroidCodec> gainmapCodec; { - ATRACE_NAME("getAndroidGainmap"); - if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) { + ATRACE_NAME("getGainmapAndroidCodec"); + if (!mCodec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec)) { return SkCodec::kSuccess; } } - auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream)); - if (!gainmapCodec) { - ALOGW("Failed to create codec for gainmap stream"); - return SkCodec::kInvalidInput; - } ImageDecoder decoder{std::move(gainmapCodec)}; // Gainmap inherits the origin of the containing image decoder.mOverrideOrigin.emplace(getOrigin()); diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index bbb142014ed8..f0bcfe537998 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -36,7 +36,7 @@ namespace android { MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize, std::string_view filePath, int ttcIndex, - const std::vector<minikin::FontVariation>& axes) + const minikin::VariationSettings& axes) : mTypeface(std::move(typeface)) , mSourceId(sourceId) , mFontData(fontData) @@ -123,12 +123,12 @@ int MinikinFontSkia::GetFontIndex() const { return mTtcIndex; } -const std::vector<minikin::FontVariation>& MinikinFontSkia::GetAxes() const { +const minikin::VariationSettings& MinikinFontSkia::GetAxes() const { return mAxes; } std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( - const std::vector<minikin::FontVariation>& variations) const { + const minikin::VariationSettings& variations) const { SkFontArguments args; std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation; diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h index de9a5c2af0aa..7fe5978bfda3 100644 --- a/libs/hwui/hwui/MinikinSkia.h +++ b/libs/hwui/hwui/MinikinSkia.h @@ -32,7 +32,7 @@ class ANDROID_API MinikinFontSkia : public minikin::MinikinFont { public: MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize, std::string_view filePath, int ttcIndex, - const std::vector<minikin::FontVariation>& axes); + const minikin::VariationSettings& axes); float GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const override; @@ -59,9 +59,9 @@ public: size_t GetFontSize() const; int GetFontIndex() const; const std::string& getFilePath() const { return mFilePath; } - const std::vector<minikin::FontVariation>& GetAxes() const; + const minikin::VariationSettings& GetAxes() const; std::shared_ptr<minikin::MinikinFont> createFontWithVariation( - const std::vector<minikin::FontVariation>&) const; + const minikin::VariationSettings&) const; int GetSourceId() const override { return mSourceId; } static uint32_t packFontFlags(const SkFont&); @@ -80,7 +80,7 @@ private: const void* mFontData; size_t mFontSize; int mTtcIndex; - std::vector<minikin::FontVariation> mAxes; + minikin::VariationSettings mAxes; std::string mFilePath; }; diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index ede385adc779..9cd6e253140e 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -48,6 +48,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.localeListId = paint->getMinikinLocaleListId(); minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); + minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant(); if (familyVariant.has_value()) { diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 708f96e5a070..7eb849fe6e3d 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -159,6 +159,14 @@ public: return SkSamplingOptions(this->filterMode()); } + void setVariationOverride(minikin::VariationSettings&& varSettings) { + mFontVariationOverride = std::move(varSettings); + } + + const minikin::VariationSettings& getFontVariationOverride() const { + return mFontVariationOverride; + } + // The Java flags (Paint.java) no longer fit into the native apis directly. // These methods handle converting to and from them and the native representations // in android::Paint. @@ -179,6 +187,7 @@ private: float mLetterSpacing = 0; float mWordSpacing = 0; std::vector<minikin::FontFeature> mFontFeatureSettings; + minikin::VariationSettings mFontVariationOverride; uint32_t mMinikinLocaleListId; std::optional<minikin::FamilyVariant> mFamilyVariant; uint32_t mHyphenEdit = 0; diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index c32ea01db7b7..6dfcedc3d918 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -39,6 +39,7 @@ Paint::Paint(const Paint& paint) , mLetterSpacing(paint.mLetterSpacing) , mWordSpacing(paint.mWordSpacing) , mFontFeatureSettings(paint.mFontFeatureSettings) + , mFontVariationOverride(paint.mFontVariationOverride) , mMinikinLocaleListId(paint.mMinikinLocaleListId) , mFamilyVariant(paint.mFamilyVariant) , mHyphenEdit(paint.mHyphenEdit) @@ -59,6 +60,7 @@ Paint& Paint::operator=(const Paint& other) { mLetterSpacing = other.mLetterSpacing; mWordSpacing = other.mWordSpacing; mFontFeatureSettings = other.mFontFeatureSettings; + mFontVariationOverride = other.mFontVariationOverride; mMinikinLocaleListId = other.mMinikinLocaleListId; mFamilyVariant = other.mFamilyVariant; mHyphenEdit = other.mHyphenEdit; @@ -76,6 +78,7 @@ bool operator==(const Paint& a, const Paint& b) { return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont && a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings && + a.mFontVariationOverride == b.mFontVariationOverride && a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index a9d1a2aed8cc..2d812d675fdc 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -92,8 +92,8 @@ Typeface* Typeface::createAbsolute(Typeface* base, int weight, bool italic) { return result; } -Typeface* Typeface::createFromTypefaceWithVariation( - Typeface* src, const std::vector<minikin::FontVariation>& variations) { +Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src, + const minikin::VariationSettings& variations) { const Typeface* resolvedFace = Typeface::resolveDefault(src); Typeface* result = new Typeface(); if (result != nullptr) { @@ -192,9 +192,8 @@ void Typeface::setRobotoTypefaceForTest() { sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData)); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont); - std::shared_ptr<minikin::MinikinFont> font = - std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont, - 0, std::vector<minikin::FontVariation>()); + std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>( + std::move(typeface), 0, data, st.st_size, kRobotoFont, 0, minikin::VariationSettings()); std::vector<std::shared_ptr<minikin::Font>> fonts; fonts.push_back(minikin::Font::Builder(font).build()); diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h index 565136e53676..2c96c1ad80fe 100644 --- a/libs/hwui/hwui/Typeface.h +++ b/libs/hwui/hwui/Typeface.h @@ -74,8 +74,8 @@ public: static Typeface* createRelative(Typeface* src, Style desiredStyle); static Typeface* createAbsolute(Typeface* base, int weight, bool italic); - static Typeface* createFromTypefaceWithVariation( - Typeface* src, const std::vector<minikin::FontVariation>& variations); + static Typeface* createFromTypefaceWithVariation(Typeface* src, + const minikin::VariationSettings& variations); static Typeface* createFromFamilies( std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic, diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 785aef312072..49a7f73fb3a3 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -183,14 +183,8 @@ static bool needsFineScale(const SkISize fullSize, const SkISize decodedSize, needsFineScale(fullSize.height(), decodedSize.height(), sampleSize); } -static bool decodeGainmap(std::unique_ptr<SkStream> gainmapStream, const SkGainmapInfo& gainmapInfo, +static bool decodeGainmap(std::unique_ptr<SkAndroidCodec> codec, const SkGainmapInfo& gainmapInfo, sp<uirenderer::Gainmap>* outGainmap, const int sampleSize, float scale) { - std::unique_ptr<SkAndroidCodec> codec; - codec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream), nullptr); - if (!codec) { - ALOGE("Can not create a codec for Gainmap."); - return false; - } SkColorType decodeColorType = kN32_SkColorType; if (codec->getInfo().colorType() == kGray_8_SkColorType) { decodeColorType = kGray_8_SkColorType; @@ -613,15 +607,15 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, bool hasGainmap = false; SkGainmapInfo gainmapInfo; - std::unique_ptr<SkStream> gainmapStream = nullptr; + std::unique_ptr<SkAndroidCodec> gainmapCodec; sp<uirenderer::Gainmap> gainmap = nullptr; if (result == SkCodec::kSuccess) { - hasGainmap = codec->getAndroidGainmap(&gainmapInfo, &gainmapStream); + hasGainmap = codec->getGainmapAndroidCodec(&gainmapInfo, &gainmapCodec); } if (hasGainmap) { hasGainmap = - decodeGainmap(std::move(gainmapStream), gainmapInfo, &gainmap, sampleSize, scale); + decodeGainmap(std::move(gainmapCodec), gainmapInfo, &gainmap, sampleSize, scale); } if (!isMutable && javaBitmap == NULL) { diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index 6a65b8273194..f7e8e073a272 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -48,25 +48,14 @@ public: } SkGainmapInfo gainmapInfo; - std::unique_ptr<SkStream> gainmapStream; + std::unique_ptr<SkAndroidCodec> gainmapCodec; std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD = nullptr; - if (mainImageBRD->getAndroidGainmap(&gainmapInfo, &gainmapStream)) { - sk_sp<SkData> data = nullptr; - if (gainmapStream->getMemoryBase()) { - // It is safe to make without copy because we'll hold onto the stream. - data = SkData::MakeWithoutCopy(gainmapStream->getMemoryBase(), - gainmapStream->getLength()); - } else { - data = SkCopyStreamToData(gainmapStream.get()); - // We don't need to hold the stream anymore - gainmapStream = nullptr; - } - gainmapBRD = skia::BitmapRegionDecoder::Make(std::move(data)); + if (!mainImageBRD->getGainmapBitmapRegionDecoder(&gainmapInfo, &gainmapBRD)) { + gainmapBRD = nullptr; } - return std::unique_ptr<BitmapRegionDecoderWrapper>( - new BitmapRegionDecoderWrapper(std::move(mainImageBRD), std::move(gainmapBRD), - gainmapInfo, std::move(gainmapStream))); + return std::unique_ptr<BitmapRegionDecoderWrapper>(new BitmapRegionDecoderWrapper( + std::move(mainImageBRD), std::move(gainmapBRD), gainmapInfo)); } SkEncodedImageFormat getEncodedFormat() { return mMainImageBRD->getEncodedFormat(); } @@ -191,16 +180,14 @@ public: private: BitmapRegionDecoderWrapper(std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD, std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD, - SkGainmapInfo info, std::unique_ptr<SkStream> stream) + SkGainmapInfo info) : mMainImageBRD(std::move(mainImageBRD)) , mGainmapBRD(std::move(gainmapBRD)) - , mGainmapInfo(info) - , mGainmapStream(std::move(stream)) {} + , mGainmapInfo(info) {} std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD; std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD; SkGainmapInfo mGainmapInfo; - std::unique_ptr<SkStream> mGainmapStream; }; } // namespace android diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index e6d790f56d0f..9922ff393e55 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -133,9 +133,9 @@ static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, in builder->axes.clear(); return false; } - std::shared_ptr<minikin::MinikinFont> minikinFont = - std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr, - fontSize, "", ttcIndex, builder->axes); + std::shared_ptr<minikin::MinikinFont> minikinFont = std::make_shared<MinikinFontSkia>( + std::move(face), fonts::getNewSourceId(), fontPtr, fontSize, "", ttcIndex, + minikin::VariationSettings(builder->axes, false)); minikin::Font::Builder fontBuilder(minikinFont); if (weight != RESOLVE_BY_FONT_TABLE) { diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 286f06a6bad8..da237928e5e1 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -1127,6 +1127,36 @@ namespace PaintGlue { return leftMinikinPaint == rightMinikinPaint; } + struct VariationBuilder { + std::vector<minikin::FontVariation> varSettings; + }; + + static jlong createFontVariationBuilder(CRITICAL_JNI_PARAMS_COMMA jint size) { + VariationBuilder* builder = new VariationBuilder(); + builder->varSettings.reserve(size); + return reinterpret_cast<jlong>(builder); + } + + static void addFontVariationToBuilder(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr, jint tag, + jfloat value) { + VariationBuilder* builder = reinterpret_cast<VariationBuilder*>(builderPtr); + builder->varSettings.emplace_back(static_cast<minikin::AxisTag>(tag), value); + } + + static void setFontVariationOverride(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, + jlong builderPtr) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + if (builderPtr == 0) { + paint->setVariationOverride(minikin::VariationSettings()); + return; + } + + VariationBuilder* builder = reinterpret_cast<VariationBuilder*>(builderPtr); + paint->setVariationOverride( + minikin::VariationSettings(builder->varSettings, false /* sorted */)); + delete builder; + } + }; // namespace PaintGlue static const JNINativeMethod methods[] = { @@ -1235,6 +1265,9 @@ static const JNINativeMethod methods[] = { {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, + {"nCreateFontVariationBuilder", "(I)J", (void*)PaintGlue::createFontVariationBuilder}, + {"nAddFontVariationToBuilder", "(JIF)V", (void*)PaintGlue::addFontVariationToBuilder}, + {"nSetFontVariationOverride", "(JJ)V", (void*)PaintGlue::setFontVariationOverride}, }; int register_android_graphics_Paint(JNIEnv* env) { diff --git a/libs/hwui/jni/PathIterator.cpp b/libs/hwui/jni/PathIterator.cpp index 3884342d8d37..e9de6555935d 100644 --- a/libs/hwui/jni/PathIterator.cpp +++ b/libs/hwui/jni/PathIterator.cpp @@ -20,6 +20,7 @@ #include "GraphicsJNI.h" #include "SkPath.h" #include "SkPoint.h" +#include "graphics_jni_helpers.h" namespace android { @@ -36,6 +37,18 @@ public: return reinterpret_cast<jlong>(new SkPath::RawIter(*path)); } + // A variant of 'next' (below) that is compatible with the host JVM. + static jint nextHost(JNIEnv* env, jclass clazz, jlong iteratorHandle, jfloatArray pointsArray) { + jfloat* points = env->GetFloatArrayElements(pointsArray, 0); +#ifdef __ANDROID__ + jint result = next(iteratorHandle, reinterpret_cast<jlong>(points)); +#else + jint result = next(env, clazz, iteratorHandle, reinterpret_cast<jlong>(points)); +#endif + env->ReleaseFloatArrayElements(pointsArray, points, 0); + return result; + } + // ---------------- @CriticalNative ------------------------- static jint peek(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle) { @@ -72,6 +85,7 @@ static const JNINativeMethod methods[] = { {"nPeek", "(J)I", (void*)SkPathIteratorGlue::peek}, {"nNext", "(JJ)I", (void*)SkPathIteratorGlue::next}, + {"nNextHost", "(J[F)I", (void*)SkPathIteratorGlue::nextHost}, }; int register_android_graphics_PathIterator(JNIEnv* env) { diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index 209b35c5537c..0f458dde8b07 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -80,7 +80,8 @@ static jlong Typeface_createFromTypefaceWithVariation(JNIEnv* env, jobject, jlon AxisHelper axis(env, axisObject); variations.push_back(minikin::FontVariation(axis.getTag(), axis.getStyleValue())); } - return toJLong(Typeface::createFromTypefaceWithVariation(toTypeface(familyHandle), variations)); + return toJLong(Typeface::createFromTypefaceWithVariation( + toTypeface(familyHandle), minikin::VariationSettings(variations, false /* sorted */))); } static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) { @@ -273,7 +274,7 @@ void MinikinFontSkiaFactory::write(minikin::BufferWriter* writer, const std::string& path = typeface->GetFontPath(); writer->writeString(path); writer->write<int>(typeface->GetFontIndex()); - const std::vector<minikin::FontVariation>& axes = typeface->GetAxes(); + const minikin::VariationSettings& axes = typeface->GetAxes(); writer->writeArray<minikin::FontVariation>(axes.data(), axes.size()); bool hasVerity = getVerity(path); writer->write<int8_t>(static_cast<int8_t>(hasVerity)); diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index f405abaaf5b4..6a05b6c2626c 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -142,7 +142,7 @@ static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>( std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(), minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(), - builder->axes); + minikin::VariationSettings(builder->axes, false)); std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont) .setWeight(weight) .setSlant(static_cast<minikin::FontStyle::Slant>(italic)) @@ -303,7 +303,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde var = reader.readArray<minikin::FontVariation>().first[index]; } else { const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface(); - var = minikinFont->GetAxes().at(index); + var = minikinFont->GetAxes()[index]; } uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value); return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary); diff --git a/libs/hwui/jni/graphics_jni_helpers.h b/libs/hwui/jni/graphics_jni_helpers.h index 78db54acc9e5..91db134af18f 100644 --- a/libs/hwui/jni/graphics_jni_helpers.h +++ b/libs/hwui/jni/graphics_jni_helpers.h @@ -80,9 +80,52 @@ static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { return static_cast<T>(res); } +// Inline variable that specifies the method binding format. +// The expected format is 'XX${method}XX', where ${method} represents the original method name. +// This variable is shared across all translation units. This is treated as a global variable as +// per C++ 17. +inline std::string jniMethodFormat; + +inline static void setJniMethodFormat(std::string value) { + jniMethodFormat = value; +} + +// Register the native methods, potenially applying the jniMethodFormat if it has been set. +static inline int jniRegisterMaybeRenamedNativeMethods(JNIEnv* env, const char* className, + const JNINativeMethod* gMethods, + int numMethods) { + if (jniMethodFormat.empty()) { + return jniRegisterNativeMethods(env, className, gMethods, numMethods); + } + + // Make a copy of gMethods with reformatted method names. + JNINativeMethod* modifiedMethods = new JNINativeMethod[numMethods]; + LOG_ALWAYS_FATAL_IF(!modifiedMethods, "Failed to allocate a copy of the JNI methods"); + + size_t methodNamePos = jniMethodFormat.find("${method}"); + LOG_ALWAYS_FATAL_IF(methodNamePos == std::string::npos, + "Invalid jniMethodFormat: could not find '${method}' in pattern"); + + for (int i = 0; i < numMethods; i++) { + modifiedMethods[i] = gMethods[i]; + std::string modifiedName = jniMethodFormat; + modifiedName.replace(methodNamePos, 9, gMethods[i].name); + char* modifiedNameChars = new char[modifiedName.length() + 1]; + LOG_ALWAYS_FATAL_IF(!modifiedNameChars, "Failed to allocate the new method name"); + std::strcpy(modifiedNameChars, modifiedName.c_str()); + modifiedMethods[i].name = modifiedNameChars; + } + int res = jniRegisterNativeMethods(env, className, modifiedMethods, numMethods); + for (int i = 0; i < numMethods; i++) { + delete[] modifiedMethods[i].name; + } + delete[] modifiedMethods; + return res; +} + static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - int res = jniRegisterNativeMethods(env, className, gMethods, numMethods); + int res = jniRegisterMaybeRenamedNativeMethods(env, className, gMethods, numMethods); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); return res; } diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index 6c05346d26da..456338631ae4 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -156,16 +156,47 @@ static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong return layout->layout.getFakery(i).isFakeItalic(); } +float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin::AxisTag tag) { + for (const minikin::FontVariation& fv : fakery.variationSettings()) { + if (fv.axisTag == tag) { + return fv.value; + } + } + return std::numeric_limits<float>::quiet_NaN(); +} + // CriticalNative static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); - return layout->layout.getFakery(i).wghtAdjustment(); + if (text_feature::typeface_redesign()) { + float value = + findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght); + if (!std::isnan(value)) { + return value; + } else { + const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i); + return font->style().weight(); + } + } else { + return layout->layout.getFakery(i).wghtAdjustment(); + } } // CriticalNative static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); - return layout->layout.getFakery(i).italAdjustment(); + if (text_feature::typeface_redesign()) { + float value = + findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital); + if (!std::isnan(value)) { + return value; + } else { + const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i); + return font->style().isItalic(); + } + } else { + return layout->layout.getFakery(i).italAdjustment(); + } } // CriticalNative diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp index e81cbfb508ae..c0ef4b14d53f 100644 --- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp +++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp @@ -29,37 +29,6 @@ namespace android { namespace uirenderer { namespace skiapipeline { -BackdropFilterDrawable::~BackdropFilterDrawable() {} - -bool BackdropFilterDrawable::prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, - int backdropImageWidth, int backdropImageHeight) { - // the drawing bounds for blurred content. - mDstBounds.setWH(properties.getWidth(), properties.getHeight()); - - float alphaMultiplier = 1.0f; - RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true); - - // get proper subset for previous content. - canvas->getTotalMatrix().mapRect(&mImageSubset, mDstBounds); - SkRect imageSubset(mImageSubset); - // ensure the subset is inside bounds of previous content. - if (!mImageSubset.intersect(SkRect::MakeWH(backdropImageWidth, backdropImageHeight))) { - return false; - } - - // correct the drawing bounds if subset was changed. - if (mImageSubset != imageSubset) { - SkMatrix inverse; - if (canvas->getTotalMatrix().invert(&inverse)) { - inverse.mapRect(&mDstBounds, mImageSubset); - } - } - - // follow the alpha from the target RenderNode. - mPaint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier); - return true; -} - void BackdropFilterDrawable::onDraw(SkCanvas* canvas) { const RenderProperties& properties = mTargetRenderNode->properties(); auto* backdropFilter = properties.layerProperties().getBackdropImageFilter(); @@ -68,27 +37,43 @@ void BackdropFilterDrawable::onDraw(SkCanvas* canvas) { return; } - auto backdropImage = surface->makeImageSnapshot(); - // sync necessary properties from target RenderNode. - if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) { + SkRect srcBounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); + + float alphaMultiplier = 1.0f; + RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true); + SkPaint paint; + paint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier); + + SkRect surfaceSubset; + canvas->getTotalMatrix().mapRect(&surfaceSubset, srcBounds); + if (!surfaceSubset.intersect(SkRect::MakeWH(surface->width(), surface->height()))) { return; } - auto imageSubset = mImageSubset.roundOut(); + auto backdropImage = surface->makeImageSnapshot(surfaceSubset.roundOut()); + + SkIRect imageBounds = SkIRect::MakeWH(backdropImage->width(), backdropImage->height()); + SkIPoint offset; + SkIRect imageSubset; + #ifdef __ANDROID__ if (canvas->recordingContext()) { backdropImage = SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter, - imageSubset, imageSubset, &mOutSubset, &mOutOffset); + imageBounds, imageBounds, &imageSubset, &offset); } else #endif { - backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset, - imageSubset, &mOutSubset, &mOutOffset); + backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageBounds, + imageBounds, &imageSubset, &offset); } - canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds, - SkSamplingOptions(SkFilterMode::kLinear), &mPaint, - SkCanvas::kStrict_SrcRectConstraint); + + canvas->save(); + canvas->resetMatrix(); + canvas->drawImageRect(backdropImage, SkRect::Make(imageSubset), surfaceSubset, + SkSamplingOptions(SkFilterMode::kLinear), &paint, + SkCanvas::kFast_SrcRectConstraint); + canvas->restore(); } } // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h index 9e35837675ae..5e216a1fc3c3 100644 --- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h +++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h @@ -37,23 +37,10 @@ public: BackdropFilterDrawable(RenderNode* renderNode, SkCanvas* canvas) : mTargetRenderNode(renderNode), mBounds(canvas->getLocalClipBounds()) {} - ~BackdropFilterDrawable(); + ~BackdropFilterDrawable() = default; private: RenderNode* mTargetRenderNode; - SkPaint mPaint; - - SkRect mDstBounds; - SkRect mImageSubset; - SkIRect mOutSubset; - SkIPoint mOutOffset; - - /** - * Check all necessary properties before actual drawing. - * Return true if ready to draw. - */ - bool prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, int backdropImageWidth, - int backdropImageHeight); protected: void onDraw(SkCanvas* canvas) override; diff --git a/libs/hwui/tests/common/scenes/BackdropBlur.cpp b/libs/hwui/tests/common/scenes/BackdropBlur.cpp new file mode 100644 index 000000000000..a1133ffe96ef --- /dev/null +++ b/libs/hwui/tests/common/scenes/BackdropBlur.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 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. + */ + +#include <SkBlendMode.h> + +#include "SkImageFilter.h" +#include "SkImageFilters.h" +#include "TestSceneBase.h" +#include "utils/Blur.h" + +class BackdropBlurAnimation : public TestScene { +private: + std::unique_ptr<TestScene> listView; + +public: + explicit BackdropBlurAnimation(const TestScene::Options& opts) { + listView.reset(TestScene::testMap()["listview"].createScene(opts)); + } + + void createContent(int width, int height, Canvas& canvas) override { + sp<RenderNode> list = TestUtils::createNode( + 0, 0, width, height, + [this, width, height](RenderProperties& props, Canvas& canvas) { + props.setClipToBounds(false); + listView->createContent(width, height, canvas); + }); + + canvas.drawRenderNode(list.get()); + + int x = width / 8; + int y = height / 4; + sp<RenderNode> blurNode = TestUtils::createNode( + x, y, width - x, height - y, [](RenderProperties& props, Canvas& canvas) { + props.mutableOutline().setRoundRect(0, 0, props.getWidth(), props.getHeight(), + dp(16), 1); + props.mutableOutline().setShouldClip(true); + sk_sp<SkImageFilter> blurFilter = SkImageFilters::Blur( + Blur::convertRadiusToSigma(dp(8)), Blur::convertRadiusToSigma(dp(8)), + SkTileMode::kClamp, nullptr, nullptr); + props.mutateLayerProperties().setBackdropImageFilter(blurFilter.get()); + canvas.drawColor(0x33000000, SkBlendMode::kSrcOver); + }); + + canvas.drawRenderNode(blurNode.get()); + } + + void doFrame(int frameNr) override { listView->doFrame(frameNr); } +}; + +static TestScene::Registrar _BackdropBlur(TestScene::Info{ + "backdropblur", "A rounded rect that does a blur-behind of a sky animation.", + [](const TestScene::Options& opts) -> test::TestScene* { + return new BackdropBlurAnimation(opts); + }}); diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index ca540874833c..4b29100c55a6 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -1280,7 +1280,7 @@ RENDERTHREAD_TEST(BackdropFilterDrawable, drawing) { canvas->drawDrawable(&backdropDrawable); // the drawable is still visible, ok to draw. EXPECT_EQ(2, canvas->mDrawCounter); - EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH - 30, CANVAS_HEIGHT - 30), canvas->mDstBounds); + EXPECT_EQ(SkRect::MakeLTRB(30, 30, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds); canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT); canvas->drawDrawable(&drawable); |