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 package com.android.wm.shell.draganddrop
17 
18 import android.app.ActivityManager
19 import android.os.RemoteException
20 import android.os.Trace
21 import android.os.Trace.TRACE_TAG_WINDOW_MANAGER
22 import android.util.Log
23 import android.view.DragEvent
24 import android.view.IWindowManager
25 import android.window.IGlobalDragListener
26 import android.window.IUnhandledDragCallback
27 import androidx.annotation.VisibleForTesting
28 import com.android.internal.protolog.common.ProtoLog
29 import com.android.wm.shell.common.ShellExecutor
30 import com.android.wm.shell.protolog.ShellProtoLogGroup
31 import java.util.function.Consumer
32 import kotlin.random.Random
33 
34 /**
35  * Manages the listener and callbacks for unhandled global drags.
36  * This is only used by DragAndDropController and should not be used directly by other classes.
37  */
38 class GlobalDragListener(
39     private val wmService: IWindowManager,
40     private val mainExecutor: ShellExecutor
41 ) {
42     private var callback: GlobalDragListenerCallback? = null
43 
44     private val globalDragListener: IGlobalDragListener =
45         object : IGlobalDragListener.Stub() {
onCrossWindowDropnull46             override fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
47                 mainExecutor.execute() {
48                     this@GlobalDragListener.onCrossWindowDrop(taskInfo)
49                 }
50             }
51 
onUnhandledDropnull52             override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
53                 mainExecutor.execute() {
54                     this@GlobalDragListener.onUnhandledDrop(event, callback)
55                 }
56             }
57         }
58 
59     /**
60      * Callbacks for global drag events.
61      */
62     interface GlobalDragListenerCallback {
63         /**
64          * Called when a global drag is successfully handled by another window.
65          */
onCrossWindowDropnull66         fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {}
67 
68         /**
69          * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
70          * dropped on a window that does not want to handle it).
71          *
72          * The implementer _must_ call onFinishedCallback, and if it consumes the drop, then it is
73          * also responsible for releasing up the drag surface provided via the drag event.
74          */
onUnhandledDropnull75         fun onUnhandledDrop(dragEvent: DragEvent, onFinishedCallback: Consumer<Boolean>) {}
76     }
77 
78     /**
79      * Sets a listener for callbacks when an unhandled drag happens.
80      */
setListenernull81     fun setListener(listener: GlobalDragListenerCallback?) {
82         val updateWm = (callback == null && listener != null)
83                 || (callback != null && listener == null)
84         callback = listener
85         if (updateWm) {
86             try {
87                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
88                     "%s unhandled drag listener",
89                     if (callback != null) "Registering" else "Unregistering")
90                 wmService.setGlobalDragListener(
91                     if (callback != null) globalDragListener else null)
92             } catch (e: RemoteException) {
93                 Log.e(TAG, "Failed to set unhandled drag listener")
94             }
95         }
96     }
97 
98     @VisibleForTesting
onCrossWindowDropnull99     fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
100         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
101             "onCrossWindowDrop: %s", taskInfo)
102         callback?.onCrossWindowDrop(taskInfo)
103     }
104 
105     @VisibleForTesting
onUnhandledDropnull106     fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
107         val traceCookie = Random.nextInt()
108         Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "GlobalDragListener.onUnhandledDrop",
109             traceCookie);
110         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
111             "onUnhandledDrop: %s", dragEvent)
112         if (callback == null) {
113             wmCallback.notifyUnhandledDropComplete(false)
114             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "GlobalDragListener.onUnhandledDrop",
115                 traceCookie);
116             return
117         }
118 
119         callback?.onUnhandledDrop(dragEvent) {
120             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
121                 "Notifying onUnhandledDrop complete: %b", it)
122             wmCallback.notifyUnhandledDropComplete(it)
123             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "GlobalDragListener.onUnhandledDrop",
124                 traceCookie);
125         }
126     }
127 
128     companion object {
129         private val TAG = GlobalDragListener::class.java.simpleName
130     }
131 }
132