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
17package com.android.systemui.shared.system;
18
19import static android.view.WindowManager.INPUT_CONSUMER_PIP;
20import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
21
22import android.os.Binder;
23import android.os.IBinder;
24import android.os.Looper;
25import android.os.RemoteException;
26import android.util.Log;
27import android.view.BatchedInputEventReceiver;
28import android.view.Choreographer;
29import android.view.InputChannel;
30import android.view.InputEvent;
31import android.view.IWindowManager;
32import android.view.MotionEvent;
33import android.view.WindowManagerGlobal;
34
35import java.io.PrintWriter;
36
37/**
38 * Manages the input consumer that allows the SystemUI to directly receive touch input.
39 */
40public class InputConsumerController {
41
42    private static final String TAG = InputConsumerController.class.getSimpleName();
43
44    /**
45     * Listener interface for callers to subscribe to touch events.
46     */
47    public interface TouchListener {
48        boolean onTouchEvent(MotionEvent ev);
49    }
50
51    /**
52     * Listener interface for callers to learn when this class is registered or unregistered with
53     * window manager
54     */
55    public interface RegistrationListener {
56        void onRegistrationChanged(boolean isRegistered);
57    }
58
59    /**
60     * Input handler used for the input consumer. Input events are batched and consumed with the
61     * SurfaceFlinger vsync.
62     */
63    private final class InputEventReceiver extends BatchedInputEventReceiver {
64
65        public InputEventReceiver(InputChannel inputChannel, Looper looper) {
66            super(inputChannel, looper, Choreographer.getSfInstance());
67        }
68
69        @Override
70        public void onInputEvent(InputEvent event, int displayId) {
71            boolean handled = true;
72            try {
73                if (mListener != null && event instanceof MotionEvent) {
74                    MotionEvent ev = (MotionEvent) event;
75                    handled = mListener.onTouchEvent(ev);
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 TouchListener mListener;
89    private RegistrationListener mRegistrationListener;
90
91    /**
92     * @param name the name corresponding to the input consumer that is defined in the system.
93     */
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     */
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     */
111    public static InputConsumerController getRecentsAnimationInputConsumer() {
112        return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(),
113                INPUT_CONSUMER_RECENTS_ANIMATION);
114    }
115
116    /**
117     * Sets the touch listener.
118     */
119    public void setTouchListener(TouchListener listener) {
120        mListener = listener;
121    }
122
123    /**
124     * Sets the registration listener.
125     */
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     */
138    public boolean isRegistered() {
139        return mInputEventReceiver != null;
140    }
141
142    /**
143     * Registers the input consumer.
144     */
145    public void registerInputConsumer() {
146        if (mInputEventReceiver == null) {
147            final InputChannel inputChannel = new InputChannel();
148            try {
149                mWindowManager.destroyInputConsumer(mName);
150                mWindowManager.createInputConsumer(mToken, mName, inputChannel);
151            } catch (RemoteException e) {
152                Log.e(TAG, "Failed to create input consumer", e);
153            }
154            mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper());
155            if (mRegistrationListener != null) {
156                mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
157            }
158        }
159    }
160
161    /**
162     * Unregisters the input consumer.
163     */
164    public void unregisterInputConsumer() {
165        if (mInputEventReceiver != null) {
166            try {
167                mWindowManager.destroyInputConsumer(mName);
168            } catch (RemoteException e) {
169                Log.e(TAG, "Failed to destroy input consumer", e);
170            }
171            mInputEventReceiver.dispose();
172            mInputEventReceiver = null;
173            if (mRegistrationListener != null) {
174                mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
175            }
176        }
177    }
178
179    public void dump(PrintWriter pw, String prefix) {
180        final String innerPrefix = prefix + "  ";
181        pw.println(prefix + TAG);
182        pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null));
183    }
184}
185