summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/gesture/EdgeGestureService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/gesture/EdgeGestureService.java')
-rw-r--r--services/java/com/android/server/gesture/EdgeGestureService.java481
1 files changed, 481 insertions, 0 deletions
diff --git a/services/java/com/android/server/gesture/EdgeGestureService.java b/services/java/com/android/server/gesture/EdgeGestureService.java
new file mode 100644
index 0000000..4ff2a5b
--- /dev/null
+++ b/services/java/com/android/server/gesture/EdgeGestureService.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project (Jens Doll)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.server.gesture;
+
+import static com.android.internal.util.gesture.EdgeServiceConstants.POSITION_MASK;
+import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_DEFAULT;
+import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_MASK;
+import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_NONE;
+import static com.android.internal.util.gesture.EdgeServiceConstants.SENSITIVITY_SHIFT;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.service.gesture.IEdgeGestureActivationListener;
+import android.service.gesture.IEdgeGestureHostCallback;
+import android.service.gesture.IEdgeGestureService;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.internal.util.gesture.EdgeGesturePosition;
+import com.android.server.gesture.EdgeGestureInputFilter;
+import com.android.server.input.InputManagerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A system service to track and handle edge swipe gestures. This service interacts with
+ * the {@link InputManagerService} to do all the dirty work for listeners:
+ * <li>Installing an input filter and listen for edge swipe gestures</li>
+ * <li>Removing those gestures from the input stream</li>
+ * <li>Transferring touch focus to new recipient</li>
+ */
+public class EdgeGestureService extends IEdgeGestureService.Stub {
+ public static final String TAG = "EdgeGestureService";
+ public static final boolean DEBUG = false;
+ public static final boolean DEBUG_INPUT = false;
+
+ public static final int MSG_EDGE_GESTURE_ACTIVATION = 32023;
+ public static final int MSG_UPDATE_SERVICE = 32025;
+
+ private final Context mContext;
+ private final InputManagerService mInputManager;
+
+ private final HandlerThread mHandlerThread = new HandlerThread("EdgeGestureHandler");
+ private Handler mHandler;
+
+ // Lock for mInputFilter, activations and listener related variables
+ private final Object mLock = new Object();
+ private EdgeGestureInputFilter mInputFilter;
+
+ private int mGlobalPositions = 0;
+ private int mGlobalSensitivity = 3;
+
+ private final class EdgeGestureActivationListenerRecord extends IEdgeGestureHostCallback.Stub implements DeathRecipient {
+ private boolean mActive;
+
+ public EdgeGestureActivationListenerRecord(IEdgeGestureActivationListener listener) {
+ this.listener = listener;
+ this.positions = 0;
+ }
+
+ public void binderDied() {
+ removeListenerRecord(this);
+ }
+
+ private void updateFlags(int flags) {
+ this.positions = flags & POSITION_MASK;
+ this.sensitivity = (flags & SENSITIVITY_MASK) >> SENSITIVITY_SHIFT;
+ }
+
+ private boolean eligibleForActivation(int positionFlag) {
+ return (positions & positionFlag) != 0;
+ }
+
+ private boolean notifyEdgeGestureActivation(int touchX, int touchY, EdgeGesturePosition position) {
+ if ((positions & position.FLAG) != 0) {
+ try {
+ mActive = true;
+ listener.onEdgeGestureActivation(touchX, touchY, position.INDEX, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify process, assuming it died.", e);
+ mActive = false;
+ binderDied();
+ }
+ }
+ return mActive;
+ }
+
+ // called through Binder
+ public boolean gainTouchFocus(IBinder windowToken) {
+ if (DEBUG) {
+ Slog.d(TAG, "Gain touch focus for " + windowToken);
+ }
+ if (mActive) {
+ return mInputFilter.unlockFilter();
+ }
+ return false;
+ }
+
+ public boolean dropEventsUntilLift() {
+ if (DEBUG) {
+ Slog.d(TAG, "Will drop all next events till touch up");
+ }
+ if (mActive) {
+ return mInputFilter.dropSequence();
+ }
+ return false;
+ }
+
+ // called through Binder
+ public void restoreListenerState() throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Restore listener state");
+ }
+ if (mActive) {
+ mInputFilter.unlockFilter();
+ mActive = false;
+ synchronized (mLock) {
+ // restore input filter state by updating
+ mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
+ }
+ }
+ }
+
+ public boolean isActive() {
+ return mActive;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.print("mPositions=0x" + Integer.toHexString(positions));
+ pw.println(" mActive=" + mActive);
+ pw.print(prefix);
+ pw.println("mBinder=" + listener);
+ }
+
+ public int positions;
+ public int sensitivity;
+ public final IEdgeGestureActivationListener listener;
+ }
+ private final List<EdgeGestureActivationListenerRecord> mEdgeGestureActivationListener =
+ new ArrayList<EdgeGestureActivationListenerRecord>();
+ // end of lock guarded variables
+
+ private DisplayObserver mDisplayObserver;
+
+ // called by system server
+ public EdgeGestureService(Context context, InputManagerService inputManager) {
+ mContext = context;
+ mInputManager = inputManager;
+ }
+
+ // called by system server
+ public void systemReady() {
+ if (DEBUG) Slog.d(TAG, "Starting the edge gesture capture thread ...");
+
+ mHandlerThread.start();
+ mHandler = new H(mHandlerThread.getLooper());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ android.os.Process.setThreadPriority(
+ android.os.Process.THREAD_PRIORITY_FOREGROUND);
+ android.os.Process.setCanSelfBackground(false);
+ }
+ });
+ mDisplayObserver = new DisplayObserver(mContext, mHandler);
+ // check if anyone registered during startup
+ mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
+ }
+
+
+ private void updateMonitoring() {
+ synchronized(mLock) {
+ mGlobalPositions = 0;
+ mGlobalSensitivity = SENSITIVITY_NONE;
+ int activePositions = 0;
+ for (EdgeGestureActivationListenerRecord temp : mEdgeGestureActivationListener) {
+ mGlobalPositions |= temp.positions;
+ if (temp.isActive()) {
+ activePositions |= temp.positions;
+ }
+ if (temp.sensitivity != SENSITIVITY_NONE) {
+ mGlobalSensitivity = Math.max(mGlobalSensitivity, temp.sensitivity);
+ }
+ }
+ boolean havePositions = mGlobalPositions != 0;
+ mGlobalPositions &= ~activePositions;
+ // if no special sensitivity is requested, we settle on DEFAULT
+ if (mGlobalSensitivity == SENSITIVITY_NONE) {
+ mGlobalSensitivity = SENSITIVITY_DEFAULT;
+ }
+
+ if (mInputFilter == null && havePositions) {
+ enforceMonitoringLocked();
+ } else if (mInputFilter != null && !havePositions) {
+ shutdownMonitoringLocked();
+ }
+ }
+ }
+
+ private void enforceMonitoringLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "Attempting to start monitoring input events ...");
+ }
+ mInputFilter = new EdgeGestureInputFilter(mContext, mHandler);
+ mInputManager.registerSecondaryInputFilter(mInputFilter);
+ mDisplayObserver.observe();
+ }
+
+ private void shutdownMonitoringLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "Shutting down monitoring input events ...");
+ }
+ mDisplayObserver.unobserve();
+ mInputManager.unregisterSecondaryInputFilter(mInputFilter);
+ mInputFilter = null;
+ }
+
+ // called through Binder
+ public IEdgeGestureHostCallback registerEdgeGestureActivationListener(IEdgeGestureActivationListener listener) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.INJECT_EVENTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Permission Denial: can't register from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+ return null;
+ }
+
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ EdgeGestureActivationListenerRecord record = null;
+ synchronized(mLock) {
+ record = findListenerRecordLocked(listener.asBinder());
+ if (record == null) {
+ record = new EdgeGestureActivationListenerRecord(listener);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Recipient died during registration pid=" + Binder.getCallingPid());
+ return null;
+ }
+ mEdgeGestureActivationListener.add(record);
+ }
+ }
+ return record;
+ }
+
+ // called through Binder
+ public void updateEdgeGestureActivationListener(IBinder listener, int positionFlags) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ synchronized(mLock) {
+ EdgeGestureActivationListenerRecord record = findListenerRecordLocked(listener);
+ if (record == null) {
+ Slog.w(TAG, "Unknown listener on update listener. Register first?");
+ throw new IllegalStateException("listener not registered");
+ }
+ record.updateFlags(positionFlags);
+ // update input filter only when #systemReady() was called
+ if (mHandler != null) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
+ }
+ }
+ }
+
+ private EdgeGestureActivationListenerRecord findListenerRecordLocked(IBinder listener) {
+ for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) {
+ if (record.listener.asBinder().equals(listener)) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ private void removeListenerRecord(EdgeGestureActivationListenerRecord record) {
+ synchronized(mLock) {
+ mEdgeGestureActivationListener.remove(record);
+ // restore input filter state by updating
+ mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
+ }
+ }
+
+ // called by handler thread
+ private boolean propagateActivation(int touchX, int touchY, EdgeGesturePosition position) {
+ synchronized(mLock) {
+ EdgeGestureActivationListenerRecord target = null;
+ for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) {
+ if (record.eligibleForActivation(position.FLAG)) {
+ target = record;
+ break;
+ }
+ }
+ // NOTE: We can do this here because the #onGestureActivation() is a oneway
+ // Binder call. This means we do not block with holding the mListenerLock!!!
+ // If this ever change, this needs to be adjusted and if you don't know what
+ // this means, you should probably not mess around with this code, anyway.
+ if (target != null && !target.notifyEdgeGestureActivation(touchX, touchY, position)) {
+ target = null;
+ }
+ return target != null;
+ }
+ }
+
+ private final class H extends Handler {
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case MSG_EDGE_GESTURE_ACTIVATION:
+ if (DEBUG) {
+ Slog.d(TAG, "Activating edge gesture on " + m.obj.toString());
+ }
+ // Since input filter runs asynchronously to us, double activation may happen
+ // theoretically. Take the safe route here.
+ removeMessages(MSG_EDGE_GESTURE_ACTIVATION);
+ if (propagateActivation(m.arg1, m.arg2, (EdgeGesturePosition) m.obj)) {
+ // switch off activated positions
+ updateMonitoring();
+ updateServiceHandler(mGlobalPositions, mGlobalSensitivity);
+ }
+ break;
+ case MSG_UPDATE_SERVICE:
+ updateMonitoring();
+ if (DEBUG) {
+ Slog.d(TAG, "Updating positions 0x" + Integer.toHexString(mGlobalPositions)
+ + " sensitivity: " + mGlobalSensitivity);
+ }
+ updateServiceHandler(mGlobalPositions, mGlobalSensitivity);
+ break;
+ }
+ }
+
+ private void updateServiceHandler(int positions, int sensitivity) {
+ synchronized (mLock) {
+ if (mInputFilter != null) {
+ mInputFilter.updatePositions(positions);
+ mInputFilter.updateSensitivity(sensitivity);
+ }
+ }
+ }
+ }
+
+ private final class DisplayObserver implements DisplayListener {
+ private final Handler mHandler;
+ private final DisplayManager mDisplayManager;
+
+ private final Display mDefaultDisplay;
+ private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+
+ public DisplayObserver(Context context, Handler handler) {
+ mHandler = handler;
+ mDisplayManager = (DisplayManager) context.getSystemService(
+ Context.DISPLAY_SERVICE);
+ final WindowManager windowManager = (WindowManager) context.getSystemService(
+ Context.WINDOW_SERVICE);
+
+ mDefaultDisplay = windowManager.getDefaultDisplay();
+ updateDisplayInfo();
+ }
+
+ private void updateDisplayInfo() {
+ if (DEBUG) {
+ Slog.d(TAG, "Updating display information ...");
+ }
+ if (mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
+ synchronized (mLock) {
+ if (mInputFilter != null) {
+ mInputFilter.updateDisplay(mDefaultDisplay, mDefaultDisplayInfo);
+ }
+ }
+ } else {
+ Slog.e(TAG, "Default display is not valid.");
+ }
+ }
+
+ public void observe() {
+ mDisplayManager.registerDisplayListener(this, mHandler);
+ updateDisplayInfo();
+ }
+
+ public void unobserve() {
+ mDisplayManager.unregisterDisplayListener(this);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ /* do noting */
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ updateDisplayInfo();
+ }
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ // let's log all exceptions we do not know about.
+ if (!(e instanceof IllegalArgumentException || e instanceof IllegalStateException)) {
+ Slog.e(TAG, "EdgeGestureService crashed: ", e);
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump EdgeGestureService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("EDGE GESTURE SERVICE (dumpsys edgegestureservice)\n");
+ synchronized(mLock) {
+ pw.println(" mInputFilter=" + mInputFilter);
+ if (mInputFilter != null) {
+ mInputFilter.dump(pw, " ");
+ }
+ pw.println(" mGlobalPositions=0x" + Integer.toHexString(mGlobalPositions));
+ pw.println(" mGlobalSensitivity=" + mGlobalSensitivity);
+ int i = 0;
+ for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) {
+ if (record.isActive()) {
+ pw.println(" Active record: #" + (i + 1));
+ }
+ }
+ i = 0;
+ for (EdgeGestureActivationListenerRecord record : mEdgeGestureActivationListener) {
+ pw.println(" Listener #" + i + ":");
+ record.dump(pw, " ");
+ i++;
+ }
+ }
+ }
+}