1 /*
2  * Copyright (C) 2017 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.systemui.shared.system;
18 
19 import static android.view.Display.DEFAULT_DISPLAY;
20 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
21 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
22 
23 import android.os.Binder;
24 import android.os.IBinder;
25 import android.os.Looper;
26 import android.os.RemoteException;
27 import android.util.Log;
28 import android.view.BatchedInputEventReceiver;
29 import android.view.Choreographer;
30 import android.view.IWindowManager;
31 import android.view.InputChannel;
32 import android.view.InputEvent;
33 import android.view.WindowManagerGlobal;
34 
35 import java.io.PrintWriter;
36 
37 /**
38  * Manages the input consumer that allows the SystemUI to directly receive input.
39  */
40 public class InputConsumerController {
41 
42     private static final String TAG = InputConsumerController.class.getSimpleName();
43 
44     /**
45      * Listener interface for callers to subscribe to input events.
46      */
47     public interface InputListener {
48         /** Handles any input event. */
onInputEvent(InputEvent ev)49         boolean onInputEvent(InputEvent ev);
50     }
51 
52     /**
53      * Listener interface for callers to learn when this class is registered or unregistered with
54      * window manager
55      */
56     public interface RegistrationListener {
onRegistrationChanged(boolean isRegistered)57         void onRegistrationChanged(boolean isRegistered);
58     }
59 
60     /**
61      * Input handler used for the input consumer. Input events are batched and consumed with the
62      * SurfaceFlinger vsync.
63      */
64     private final class InputEventReceiver extends BatchedInputEventReceiver {
65 
InputEventReceiver(InputChannel inputChannel, Looper looper)66         public InputEventReceiver(InputChannel inputChannel, Looper looper) {
67             super(inputChannel, looper, Choreographer.getSfInstance());
68         }
69 
70         @Override
onInputEvent(InputEvent event)71         public void onInputEvent(InputEvent event) {
72             boolean handled = true;
73             try {
74                 if (mListener != null) {
75                     handled = mListener.onInputEvent(event);
76                 }
77             } finally {
78                 finishInputEvent(event, handled);
79             }
80         }
81     }
82 
83     private final IWindowManager mWindowManager;
84     private final IBinder mToken;
85     private final String mName;
86 
87     private InputEventReceiver mInputEventReceiver;
88     private InputListener mListener;
89     private RegistrationListener mRegistrationListener;
90 
91     /**
92      * @param name the name corresponding to the input consumer that is defined in the system.
93      */
InputConsumerController(IWindowManager windowManager, String name)94     public InputConsumerController(IWindowManager windowManager, String name) {
95         mWindowManager = windowManager;
96         mToken = new Binder();
97         mName = name;
98     }
99 
100     /**
101      * @return A controller for the pip input consumer.
102      */
getPipInputConsumer()103     public static InputConsumerController getPipInputConsumer() {
104         return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(),
105                 INPUT_CONSUMER_PIP);
106     }
107 
108     /**
109      * @return A controller for the recents animation input consumer.
110      */
getRecentsAnimationInputConsumer()111     public static InputConsumerController getRecentsAnimationInputConsumer() {
112         return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(),
113                 INPUT_CONSUMER_RECENTS_ANIMATION);
114     }
115 
116     /**
117      * Sets the input listener.
118      */
setInputListener(InputListener listener)119     public void setInputListener(InputListener listener) {
120         mListener = listener;
121     }
122 
123     /**
124      * Sets the registration listener.
125      */
setRegistrationListener(RegistrationListener listener)126     public void setRegistrationListener(RegistrationListener listener) {
127         mRegistrationListener = listener;
128         if (mRegistrationListener != null) {
129             mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
130         }
131     }
132 
133     /**
134      * Check if the InputConsumer is currently registered with WindowManager
135      *
136      * @return {@code true} if registered, {@code false} if not.
137      */
isRegistered()138     public boolean isRegistered() {
139         return mInputEventReceiver != null;
140     }
141 
142     /**
143      * Registers the input consumer.
144      */
registerInputConsumer()145     public void registerInputConsumer() {
146         if (mInputEventReceiver == null) {
147             final InputChannel inputChannel = new InputChannel();
148             try {
149                 // TODO(b/113087003): Support Picture-in-picture in multi-display.
150                 mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
151                 mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
152             } catch (RemoteException e) {
153                 Log.e(TAG, "Failed to create input consumer", e);
154             }
155             mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper());
156             if (mRegistrationListener != null) {
157                 mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
158             }
159         }
160     }
161 
162     /**
163      * Unregisters the input consumer.
164      */
unregisterInputConsumer()165     public void unregisterInputConsumer() {
166         if (mInputEventReceiver != null) {
167             try {
168                 // TODO(b/113087003): Support Picture-in-picture in multi-display.
169                 mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY);
170             } catch (RemoteException e) {
171                 Log.e(TAG, "Failed to destroy input consumer", e);
172             }
173             mInputEventReceiver.dispose();
174             mInputEventReceiver = null;
175             if (mRegistrationListener != null) {
176                 mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
177             }
178         }
179     }
180 
dump(PrintWriter pw, String prefix)181     public void dump(PrintWriter pw, String prefix) {
182         final String innerPrefix = prefix + "  ";
183         pw.println(prefix + TAG);
184         pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null));
185     }
186 }
187