1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.desktopmode 18 19 import android.app.ActivityManager.RunningTaskInfo 20 import android.content.Context 21 import android.content.pm.ActivityInfo 22 import android.content.pm.ActivityInfo.ScreenOrientation 23 import android.content.res.Configuration.ORIENTATION_LANDSCAPE 24 import android.content.res.Configuration.ORIENTATION_PORTRAIT 25 import android.graphics.Rect 26 import android.window.WindowContainerTransaction 27 import com.android.window.flags.Flags 28 import com.android.wm.shell.ShellTaskOrganizer 29 import com.android.wm.shell.common.DisplayController 30 import com.android.wm.shell.common.TaskStackListenerCallback 31 import com.android.wm.shell.common.TaskStackListenerImpl 32 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus 33 import com.android.wm.shell.sysui.ShellInit 34 35 /** Handles task resizing to respect orientation change of non-resizeable activities in desktop. */ 36 class DesktopActivityOrientationChangeHandler( 37 context: Context, 38 shellInit: ShellInit, 39 private val shellTaskOrganizer: ShellTaskOrganizer, 40 private val taskStackListener: TaskStackListenerImpl, 41 private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler, 42 private val desktopUserRepositories: DesktopUserRepositories, 43 private val displayController: DisplayController, 44 ) { 45 46 init { 47 if (DesktopModeStatus.canEnterDesktopMode(context)) { <lambda>null48 shellInit.addInitCallback({ onInit() }, this) 49 } 50 } 51 onInitnull52 private fun onInit() { 53 taskStackListener.addListener( 54 object : TaskStackListenerCallback { 55 override fun onActivityRequestedOrientationChanged( 56 taskId: Int, 57 @ScreenOrientation requestedOrientation: Int, 58 ) { 59 // Handle requested screen orientation changes at runtime. 60 handleActivityOrientationChange(taskId, requestedOrientation) 61 } 62 } 63 ) 64 } 65 66 /** 67 * Triggered with onTaskInfoChanged to handle: 68 * * New activity launching from same task with different orientation 69 * * Top activity closing in same task with different orientation to previous activity 70 */ handleActivityOrientationChangenull71 fun handleActivityOrientationChange(oldTask: RunningTaskInfo, newTask: RunningTaskInfo) { 72 val newTopActivityInfo = newTask.topActivityInfo ?: return 73 val oldTopActivityInfo = oldTask.topActivityInfo ?: return 74 // Check if screen orientation is different from old task info so there is no duplicated 75 // calls to handle runtime requested orientation changes. 76 if (oldTopActivityInfo.screenOrientation != newTopActivityInfo.screenOrientation) { 77 handleActivityOrientationChange(newTask.taskId, newTopActivityInfo.screenOrientation) 78 } 79 } 80 handleActivityOrientationChangenull81 private fun handleActivityOrientationChange( 82 taskId: Int, 83 @ScreenOrientation requestedOrientation: Int, 84 ) { 85 if (!Flags.respectOrientationChangeForUnresizeable()) return 86 val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return 87 val taskRepository = desktopUserRepositories.current 88 val isDesktopModeShowing = taskRepository.isAnyDeskActive(task.displayId) 89 if (!isDesktopModeShowing || !task.isFreeform || task.isResizeable) return 90 91 val taskBounds = task.configuration.windowConfiguration.bounds 92 val taskHeight = taskBounds.height() 93 val taskWidth = taskBounds.width() 94 if (taskWidth == taskHeight) return 95 val orientation = 96 if (taskWidth > taskHeight) ORIENTATION_LANDSCAPE else ORIENTATION_PORTRAIT 97 98 // Non-resizeable activity requested opposite orientation. 99 if ( 100 orientation == ORIENTATION_PORTRAIT && 101 ActivityInfo.isFixedOrientationLandscape(requestedOrientation) || 102 orientation == ORIENTATION_LANDSCAPE && 103 ActivityInfo.isFixedOrientationPortrait(requestedOrientation) 104 ) { 105 val displayLayout = displayController.getDisplayLayout(task.displayId) ?: return 106 val captionInsets = 107 task.configuration.windowConfiguration.appBounds?.let { 108 it.top - task.configuration.windowConfiguration.bounds.top 109 } ?: 0 110 val newOrientationBounds = 111 calculateInitialBounds( 112 displayLayout = displayLayout, 113 taskInfo = task, 114 captionInsets = captionInsets, 115 requestedScreenOrientation = requestedOrientation, 116 ) 117 118 // Use the center x as the resizing anchor point. 119 val left = taskBounds.centerX() - newOrientationBounds.width() / 2 120 val right = left + newOrientationBounds.width() 121 val finalBounds = 122 Rect(left, taskBounds.top, right, taskBounds.top + newOrientationBounds.height()) 123 124 val wct = WindowContainerTransaction().setBounds(task.token, finalBounds) 125 resizeHandler.startTransition(wct) 126 } 127 } 128 } 129