1 /* 2 * Copyright (C) 2020 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.quickstep.util; 17 18 import static android.view.MotionEvent.ACTION_CANCEL; 19 import static android.view.MotionEvent.ACTION_DOWN; 20 import static android.view.MotionEvent.ACTION_UP; 21 22 import android.content.Context; 23 import android.util.Log; 24 import android.view.InputEvent; 25 import android.view.KeyEvent; 26 import android.view.MotionEvent; 27 28 import com.android.quickstep.InputConsumer; 29 import com.android.quickstep.SimpleOrientationTouchTransformer; 30 import com.android.systemui.shared.system.InputConsumerController; 31 32 import java.util.function.Supplier; 33 34 /** 35 * Utility class which manages proxying input events from {@link InputConsumerController} 36 * to an {@link InputConsumer} 37 */ 38 public class InputConsumerProxy { 39 40 private static final String TAG = "InputConsumerProxy"; 41 42 private final Context mContext; 43 private final Supplier<Integer> mRotationSupplier; 44 private final InputConsumerController mInputConsumerController; 45 private Runnable mCallback; 46 private Supplier<InputConsumer> mConsumerSupplier; 47 48 // The consumer is created lazily on demand. 49 private InputConsumer mInputConsumer; 50 51 private boolean mDestroyed = false; 52 private boolean mTouchInProgress = false; 53 private boolean mDestroyPending = false; 54 InputConsumerProxy(Context context, Supplier<Integer> rotationSupplier, InputConsumerController inputConsumerController, Runnable callback, Supplier<InputConsumer> consumerSupplier)55 public InputConsumerProxy(Context context, Supplier<Integer> rotationSupplier, 56 InputConsumerController inputConsumerController, 57 Runnable callback, Supplier<InputConsumer> consumerSupplier) { 58 mContext = context; 59 mRotationSupplier = rotationSupplier; 60 mInputConsumerController = inputConsumerController; 61 mCallback = callback; 62 mConsumerSupplier = consumerSupplier; 63 } 64 enable()65 public void enable() { 66 if (mDestroyed) { 67 return; 68 } 69 mInputConsumerController.setInputListener(this::onInputConsumerEvent); 70 } 71 onInputConsumerEvent(InputEvent ev)72 private boolean onInputConsumerEvent(InputEvent ev) { 73 if (ev instanceof MotionEvent) { 74 MotionEvent event = (MotionEvent) ev; 75 int action = event.getActionMasked(); 76 boolean isHoverEvent = action == MotionEvent.ACTION_HOVER_ENTER 77 || action == MotionEvent.ACTION_HOVER_MOVE 78 || action == MotionEvent.ACTION_HOVER_EXIT; 79 if (isHoverEvent) { 80 onInputConsumerHoverEvent(event); 81 } else { 82 onInputConsumerMotionEvent(event); 83 } 84 } else if (ev instanceof KeyEvent) { 85 initInputConsumerIfNeeded(); 86 mInputConsumer.onKeyEvent((KeyEvent) ev); 87 return true; 88 } 89 return false; 90 } 91 onInputConsumerMotionEvent(MotionEvent ev)92 private boolean onInputConsumerMotionEvent(MotionEvent ev) { 93 int action = ev.getAction(); 94 95 // Just to be safe, verify that ACTION_DOWN comes before any other action, 96 // and ignore any ACTION_DOWN after the first one (though that should not happen). 97 if (!mTouchInProgress && action != ACTION_DOWN) { 98 Log.w(TAG, "Received non-down motion before down motion: " + action); 99 return false; 100 } 101 if (mTouchInProgress && action == ACTION_DOWN) { 102 Log.w(TAG, "Received down motion while touch was already in progress"); 103 return false; 104 } 105 106 if (action == ACTION_DOWN) { 107 mTouchInProgress = true; 108 initInputConsumerIfNeeded(); 109 } else if (action == ACTION_CANCEL || action == ACTION_UP) { 110 // Finish any pending actions 111 mTouchInProgress = false; 112 if (mDestroyPending) { 113 destroy(); 114 } 115 } 116 if (mInputConsumer != null) { 117 SimpleOrientationTouchTransformer.INSTANCE.get(mContext).transform(ev, 118 mRotationSupplier.get()); 119 mInputConsumer.onMotionEvent(ev); 120 } 121 122 return true; 123 } 124 onInputConsumerHoverEvent(MotionEvent ev)125 private void onInputConsumerHoverEvent(MotionEvent ev) { 126 initInputConsumerIfNeeded(); 127 if (mInputConsumer != null) { 128 SimpleOrientationTouchTransformer.INSTANCE.get(mContext).transform(ev, 129 mRotationSupplier.get()); 130 mInputConsumer.onHoverEvent(ev); 131 } 132 } 133 destroy()134 public void destroy() { 135 if (mTouchInProgress) { 136 mDestroyPending = true; 137 return; 138 } 139 mDestroyPending = false; 140 mDestroyed = true; 141 mInputConsumerController.setInputListener(null); 142 } 143 unregisterCallback()144 public void unregisterCallback() { 145 mCallback = null; 146 } 147 initInputConsumerIfNeeded()148 private void initInputConsumerIfNeeded() { 149 if (mInputConsumer == null) { 150 if (mCallback != null) { 151 mCallback.run(); 152 } 153 mInputConsumer = mConsumerSupplier.get(); 154 mConsumerSupplier = null; 155 } 156 } 157 } 158