/* * 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 static com.android.internal.util.gesture.EdgeServiceConstants.LONG_LIVING; 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: *
  • Installing an input filter and listen for edge swipe gestures
  • *
  • Removing those gestures from the input stream
  • *
  • Transferring touch focus to new recipient
  • */ 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; this.longLiving = (flags & LONG_LIVING) != 0; } 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; public boolean longLiving = false; } private final List mEdgeGestureActivationListener = new ArrayList(); // 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; boolean someLongLiving = false; 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); } someLongLiving |= temp.longLiving; } 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 && !someLongLiving) { 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++; } } } }