• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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