summaryrefslogtreecommitdiffstats
path: root/services/java/com
diff options
context:
space:
mode:
authorJens Doll <jens.doll@gmail.com>2013-04-17 17:32:04 +0200
committerDanny Baumann <dannybaumann@web.de>2013-05-15 13:44:23 +0200
commit9f11bd11a1389bb98d54f6e6114028f4e39f90e1 (patch)
tree9ca53656e66ccc069f452ed8f7544f6aaf6613cf /services/java/com
parentcdd98977f5089eb829724579943dbe23480ea272 (diff)
downloadframeworks_base-9f11bd11a1389bb98d54f6e6114028f4e39f90e1.zip
frameworks_base-9f11bd11a1389bb98d54f6e6114028f4e39f90e1.tar.gz
frameworks_base-9f11bd11a1389bb98d54f6e6114028f4e39f90e1.tar.bz2
Pie controls: Introducing a pie delivery service
To make pie controls more reliable, it is neccessary to detect trigger actions directly from the input stream. This commit introduces a new system service, that filters all input events in front of the input dispatcher to detect pie activations. This commit introduces: * A new system server local API in the input manager service to register secondary input filters. These filters are behind the default accessibility filter but before the input event dispatching of the android framework. * A new system service, that binds to the new API to listen for pie activation gestures. * A non-public manager class providing access to the newly created pie service. The service manager name of the service is "pieservice". The non-public AIDL interface of the service is IPieService.aidl. To register a new pie activation listener the INJECT_INPUT permission is needed. The service state can be dumped by the "dumpsys pieservice" command. Note: This commit only introduces the pie service. There is another commit, that binds the actual pie controls to the pie service. Patch Set #1: * The pie service is currently disabled by default and needs to be enabled by device overlays (see config.xml / config_allowPieService). Patch Set #2: * Activation fixes * Debug dump improvements Patch Set #4: * Added systrace support (TRACE_INPUT_TAG) * Switch default to enable service on all devices. * Moved Position to com.internal.android.utils.pie.* * Some more code rearrangements Patch Set #5: * Rebase Patch Set #6: * Cover more corner cases on PieInputFilter * Adjust gesture time out Patch Set #7: * Do not send events that are from the past * Recycle all events Patch Set #8: * Handle binder died events in PieService correctly Patch Set #10: * Simplified locking * SYSTEM_UI_FLAG_HIDE_NAVIGATION support * Fixed ADW Lauchner bug Change-Id: I6a4a4635bed420e800a3230457ee690131116a11
Diffstat (limited to 'services/java/com')
-rw-r--r--services/java/com/android/server/SystemServer.java21
-rw-r--r--services/java/com/android/server/input/InputManagerService.java136
-rw-r--r--services/java/com/android/server/pie/PieGestureTracker.java221
-rw-r--r--services/java/com/android/server/pie/PieInputFilter.java510
-rw-r--r--services/java/com/android/server/pie/PieService.java469
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java33
6 files changed, 1363 insertions, 27 deletions
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b2ea635..1b2b65d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -62,6 +62,7 @@ import com.android.server.dreams.DreamManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
+import com.android.server.pie.PieService;
import com.android.server.pm.Installer;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
@@ -383,6 +384,7 @@ class ServerThread extends Thread {
TextServicesManagerService tsms = null;
LockSettingsService lockSettings = null;
DreamManagerService dreamy = null;
+ PieService pieService = null;
// Bring up services needed for UI.
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
@@ -818,6 +820,17 @@ class ServerThread extends Thread {
} catch (Throwable e) {
Slog.e(TAG, "Failure starting AssetRedirectionManager Service", e);
}
+
+ if (context.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowPieService)) {
+ try {
+ Slog.i(TAG, "Pie Delivery Service");
+ pieService = new PieService(context, wm, inputManager);
+ ServiceManager.addService("pieservice", pieService);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting Pie Delivery Service Service", e);
+ }
+ }
}
// make sure the ADB_ENABLED setting value matches the secure property value
@@ -910,6 +923,14 @@ class ServerThread extends Thread {
reportWtf("making Display Manager Service ready", e);
}
+ if (pieService != null) {
+ try {
+ pieService.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making Pie Delivery Service ready", e);
+ }
+ }
+
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE);
filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE_RESET);
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 9921b5c..aa3b283 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -97,6 +97,7 @@ public class InputManagerService extends IInputManager.Stub
implements Watchdog.Monitor, DisplayManagerService.InputManagerFuncs {
static final String TAG = "InputManager";
static final boolean DEBUG = false;
+ static final boolean DEBUG_FILTER = false;
private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
@@ -143,8 +144,9 @@ public class InputManagerService extends IInputManager.Stub
// State for the currently installed input filter.
final Object mInputFilterLock = new Object();
- IInputFilter mInputFilter; // guarded by mInputFilterLock
- InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
+ ChainedInputFilterHost mInputFilterHost; // guarded by mInputFilterLock
+ ArrayList<ChainedInputFilterHost> mInputFilterChain =
+ new ArrayList<ChainedInputFilterHost>(); // guarded by mInputFilterLock
private static native int nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
@@ -475,34 +477,77 @@ public class InputManagerService extends IInputManager.Stub
*/
public void setInputFilter(IInputFilter filter) {
synchronized (mInputFilterLock) {
- final IInputFilter oldFilter = mInputFilter;
+ final IInputFilter oldFilter = mInputFilterHost != null
+ ? mInputFilterHost.mInputFilter : null;
if (oldFilter == filter) {
return; // nothing to do
}
if (oldFilter != null) {
- mInputFilter = null;
mInputFilterHost.disconnectLocked();
+ mInputFilterChain.remove(mInputFilterHost);
mInputFilterHost = null;
- try {
- oldFilter.uninstall();
- } catch (RemoteException re) {
- /* ignore */
- }
}
if (filter != null) {
- mInputFilter = filter;
- mInputFilterHost = new InputFilterHost();
- try {
- filter.install(mInputFilterHost);
- } catch (RemoteException re) {
- /* ignore */
+ ChainedInputFilterHost head = mInputFilterChain.isEmpty() ? null :
+ mInputFilterChain.get(0);
+ mInputFilterHost = new ChainedInputFilterHost(filter, head);
+ mInputFilterHost.connectLocked();
+ mInputFilterChain.add(0, mInputFilterHost);
+ }
+
+ nativeSetInputFilterEnabled(mPtr, !mInputFilterChain.isEmpty());
+ }
+ }
+
+ /**
+ * Registers a secondary input filter. These filters are always behind the "original"
+ * input filter. This ensures that all input events will be filtered by the
+ * {@code AccessibilityManagerService} first.
+ * <p>
+ * <b>Note:</b> Even though this implementation using AIDL interfaces, it is designed to only
+ * provide direct access. Therefore, any filter registering should reside in the
+ * system server DVM only!
+ *
+ * @param filter The input filter to register.
+ */
+ public void registerSecondaryInputFilter(IInputFilter filter) {
+ synchronized (mInputFilterLock) {
+ ChainedInputFilterHost host = new ChainedInputFilterHost(filter, null);
+ if (!mInputFilterChain.isEmpty()) {
+ mInputFilterChain.get(mInputFilterChain.size() - 1).mNext = host;
+ }
+ host.connectLocked();
+ mInputFilterChain.add(host);
+
+ nativeSetInputFilterEnabled(mPtr, !mInputFilterChain.isEmpty());
+ }
+ }
+
+ public void unregisterSecondaryInputFilter(IInputFilter filter) {
+ synchronized (mInputFilterLock) {
+ int index = findInputFilterIndexLocked(filter);
+ if (index >= 0) {
+ ChainedInputFilterHost host = mInputFilterChain.get(index);
+ host.disconnectLocked();
+ if (index >= 1) {
+ mInputFilterChain.get(index - 1).mNext = host.mNext;
}
+ mInputFilterChain.remove(index);
}
- nativeSetInputFilterEnabled(mPtr, filter != null);
+ nativeSetInputFilterEnabled(mPtr, !mInputFilterChain.isEmpty());
+ }
+ }
+
+ private int findInputFilterIndexLocked(IInputFilter filter) {
+ for (int i = 0; i < mInputFilterChain.size(); i++) {
+ if (mInputFilterChain.get(i).mInputFilter == filter) {
+ return i;
+ }
}
+ return -1;
}
@Override // Binder call
@@ -1327,16 +1372,23 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
final boolean filterInputEvent(InputEvent event, int policyFlags) {
+ ChainedInputFilterHost head = null;
synchronized (mInputFilterLock) {
- if (mInputFilter != null) {
- try {
- mInputFilter.filterInputEvent(event, policyFlags);
- } catch (RemoteException e) {
- /* ignore */
- }
- return false;
+ if (!mInputFilterChain.isEmpty()) {
+ head = mInputFilterChain.get(0);
}
}
+ // call filter input event outside of the lock.
+ // this is safe, because we know that mInputFilter never changes.
+ // we may loose a event, but this does not differ from the original implementation.
+ if (head != null) {
+ try {
+ head.mInputFilter.filterInputEvent(event, policyFlags);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ return false;
+ }
event.recycle();
return true;
}
@@ -1561,10 +1613,32 @@ public class InputManagerService extends IInputManager.Stub
/**
* Hosting interface for input filters to call back into the input manager.
*/
- private final class InputFilterHost extends IInputFilterHost.Stub {
+ private final class ChainedInputFilterHost extends IInputFilterHost.Stub {
+ private final IInputFilter mInputFilter;
+ private ChainedInputFilterHost mNext;
private boolean mDisconnected;
+ private ChainedInputFilterHost(IInputFilter filter, ChainedInputFilterHost next) {
+ mInputFilter = filter;
+ mNext = next;
+ mDisconnected = false;
+ }
+
+ public void connectLocked() {
+ try {
+ mInputFilter.install(this);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
public void disconnectLocked() {
+ try {
+ mInputFilter.uninstall();
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ // DO NOT set mInputFilter to null here! mInputFilter is used outside of the lock!
mDisconnected = true;
}
@@ -1576,9 +1650,17 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mInputFilterLock) {
if (!mDisconnected) {
- nativeInjectInputEvent(mPtr, event, 0, 0,
- InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
- policyFlags | WindowManagerPolicy.FLAG_FILTERED);
+ if (mNext == null) {
+ nativeInjectInputEvent(mPtr, event, 0, 0,
+ InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
+ policyFlags | WindowManagerPolicy.FLAG_FILTERED);
+ } else {
+ try {
+ mNext.mInputFilter.filterInputEvent(event, policyFlags);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
}
}
}
diff --git a/services/java/com/android/server/pie/PieGestureTracker.java b/services/java/com/android/server/pie/PieGestureTracker.java
new file mode 100644
index 0000000..d992657
--- /dev/null
+++ b/services/java/com/android/server/pie/PieGestureTracker.java
@@ -0,0 +1,221 @@
+/*
+ * 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.pie;
+
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.Display;
+import android.view.MotionEvent;
+
+import com.android.internal.util.pie.PiePosition;
+
+/**
+ * A simple {@link MotionEvent} tracker class. The main aim of this tracker is to
+ * reject gestures as fast as possible, so there is only a small amount of events
+ * that will be delayed.
+ */
+public class PieGestureTracker {
+ public final static String TAG = "PieTracker";
+ public final static boolean DEBUG = false;
+
+ public final static long TRIGGER_TIME_MS = 140;
+ public final static int PIXEL_SWIPE_OFFTAKE_SLOP = 2;
+
+ private final int mTriggerThickness;
+ private final int mTriggerDistance;
+ private final int mPerpendicularDistance;
+
+ private int mDisplayWidth;
+ private int mDisplayHeight;
+
+ private boolean mActive;
+ private PiePosition mPosition;
+ private long mDownTime;
+ private int mInitialX;
+ private int mInitialY;
+ private int mOffTake;
+ private int mGracePeriod;
+
+ public interface OnActivationListener {
+ public void onActivation(MotionEvent event, int touchX, int touchY, PiePosition position);
+ }
+ private OnActivationListener mActivationListener;
+
+ public PieGestureTracker(int thickness, int distance, int perpendicular) {
+ if (DEBUG) {
+ Slog.d(TAG, "init: " + thickness + "," + distance);
+ }
+ mTriggerThickness = thickness;
+ mTriggerDistance = distance;
+ mPerpendicularDistance = perpendicular;
+ }
+
+ public void setOnActivationListener(OnActivationListener listener) {
+ mActivationListener = listener;
+ }
+
+ public void reset() {
+ mActive = false;
+ }
+
+ public void updateDisplay(Display display) {
+ Point outSize = new Point(0,0);
+ display.getSize(outSize);
+ mDisplayWidth = outSize.x;
+ mDisplayHeight = outSize.y;
+ if (DEBUG) {
+ Slog.d(TAG, "new display: " + mDisplayWidth + "," + mDisplayHeight);
+ }
+ }
+
+ public boolean start(MotionEvent motionEvent, int positions) {
+ final int x = (int) motionEvent.getX();
+ final float fx = motionEvent.getX() / mDisplayWidth;
+ final int y = (int) motionEvent.getY();
+ final float fy = motionEvent.getY() / mDisplayHeight;
+
+ if ((positions & PiePosition.LEFT.FLAG) != 0) {
+ if (x < mTriggerThickness && fy > 0.1f && fy < 0.9f) {
+ startWithPosition(motionEvent, PiePosition.LEFT);
+ return true;
+ }
+ }
+ if ((positions & PiePosition.BOTTOM.FLAG) != 0) {
+ if (y > mDisplayHeight - mTriggerThickness && fx > 0.1f && fx < 0.9f) {
+ startWithPosition(motionEvent, PiePosition.BOTTOM);
+ return true;
+ }
+ }
+ if ((positions & PiePosition.RIGHT.FLAG) != 0) {
+ if (x > mDisplayWidth - mTriggerThickness && fy > 0.1f && fy < 0.9f) {
+ startWithPosition(motionEvent, PiePosition.RIGHT);
+ return true;
+ }
+ }
+ if ((positions & PiePosition.TOP.FLAG) != 0) {
+ if (y < mTriggerThickness && fx > 0.1f && fx < 0.9f) {
+ startWithPosition(motionEvent, PiePosition.TOP);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void startWithPosition(MotionEvent motionEvent, PiePosition position) {
+ if (DEBUG) {
+ Slog.d(TAG, "start tracking from " + position.name());
+ }
+
+ mDownTime = motionEvent.getDownTime();
+ this.mPosition = position;
+ mInitialX = (int) motionEvent.getX();
+ mInitialY = (int) motionEvent.getY();
+ switch (position) {
+ case LEFT:
+ mGracePeriod = (int) (mTriggerDistance / 3.0f);
+ mOffTake = mInitialX - PIXEL_SWIPE_OFFTAKE_SLOP;
+ break;
+ case BOTTOM:
+ mOffTake = mInitialY + PIXEL_SWIPE_OFFTAKE_SLOP;
+ break;
+ case RIGHT:
+ mGracePeriod = mDisplayWidth - (int) (mTriggerDistance / 3.0f);
+ mOffTake = mInitialX + PIXEL_SWIPE_OFFTAKE_SLOP;
+ break;
+ case TOP:
+ mOffTake = mInitialY - PIXEL_SWIPE_OFFTAKE_SLOP;
+ break;
+ }
+ mActive = true;
+ }
+
+ public boolean move(MotionEvent motionEvent) {
+ if (!mActive || motionEvent.getEventTime() - mDownTime > TRIGGER_TIME_MS) {
+ Slog.d(TAG, "pie gesture timeout: " + (motionEvent.getEventTime() - mDownTime));
+ mActive = false;
+ return false;
+ }
+
+ final int x = (int) motionEvent.getX();
+ final int y = (int) motionEvent.getY();
+ final int deltaX = x - mInitialX;
+ final int deltaY = y - mInitialY;
+
+ if (DEBUG) {
+ Slog.d(TAG, "move at " + x + "," + y + " " + deltaX + "," + deltaY);
+ }
+
+ boolean loaded = false;
+ switch (mPosition) {
+ case LEFT:
+ if (x < mGracePeriod) {
+ mInitialY = y;
+ }
+ if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance
+ && x >= mOffTake) {
+ if (deltaX < mTriggerDistance) {
+ mOffTake = x - PIXEL_SWIPE_OFFTAKE_SLOP;
+ return true;
+ }
+ loaded = true;
+ }
+ break;
+ case BOTTOM:
+ if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance
+ && y <= mOffTake) {
+ if (deltaY > -mTriggerDistance) {
+ mOffTake = y + PIXEL_SWIPE_OFFTAKE_SLOP;
+ return true;
+ }
+ loaded = true;
+ }
+ break;
+ case RIGHT:
+ if (x > mGracePeriod) {
+ mInitialY = y;
+ }
+ if (deltaY < mPerpendicularDistance && deltaY > -mPerpendicularDistance
+ && x <= mOffTake) {
+ if (deltaX > -mTriggerDistance) {
+ mOffTake = x + PIXEL_SWIPE_OFFTAKE_SLOP;
+ return true;
+ }
+ loaded = true;
+ }
+ break;
+ case TOP:
+ if (deltaX < mPerpendicularDistance && deltaX > -mPerpendicularDistance
+ && y >= mOffTake) {
+ if (deltaY < mTriggerDistance) {
+ mOffTake = y - PIXEL_SWIPE_OFFTAKE_SLOP;
+ return true;
+ }
+ loaded = true;
+ }
+ break;
+ }
+ mActive = false;
+ if (loaded && mActivationListener != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "activate at " + x + "," + y + " " + mPosition + " within "
+ + (SystemClock.uptimeMillis() - mDownTime) + "ms");
+ }
+ mActivationListener.onActivation(motionEvent, x, y, mPosition);
+ }
+ return loaded;
+ }
+} \ No newline at end of file
diff --git a/services/java/com/android/server/pie/PieInputFilter.java b/services/java/com/android/server/pie/PieInputFilter.java
new file mode 100644
index 0000000..00cfc14
--- /dev/null
+++ b/services/java/com/android/server/pie/PieInputFilter.java
@@ -0,0 +1,510 @@
+/*
+ * 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.pie;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.IInputFilter;
+import android.view.IInputFilterHost;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+
+import com.android.internal.R;
+import com.android.internal.util.pie.PiePosition;
+import com.android.server.pie.PieGestureTracker.OnActivationListener;
+
+import java.io.PrintWriter;
+
+/**
+ * A simple input filter, that listens for pie activation gestures in the motion event input
+ * stream.
+ * <p>
+ * There are 5 distinct states of this filter.
+ * 1) LISTEN:
+ * mTracker.active == false
+ * All motion events are passed through. If a ACTION_DOWN within a pie trigger area happen
+ * switch to DETECTING.
+ * 2) DETECTING:
+ * mTracker.active == true
+ * All events are buffered now, and the gesture is checked by mTracker. If mTracker rejects
+ * the gesture (hopefully as fast as possible) all cached events will be flushed out and the
+ * filter falls back to LISTEN.
+ * If mTracker accepts the gesture, clear all cached events and go to LOCKED.
+ * 3) LOCKED:
+ * mTracker.active == false
+ * All events will be cached until the state changes to SYNTHESIZE through a filter
+ * unlock event. If there is a ACTION_UP, _CANCEL or any PointerId differently to the last
+ * event seen when mTracker accepted the gesture, we flush all events and go to LISTEN.
+ * 4) SYNTHESIZE:
+ * The first motion event found will be turned into a ACTION_DOWN event, all previous events
+ * will be discarded.
+ * 5) POSTSYNTHESIZE:
+ * mSyntheticDownTime != -1
+ * All following events will have the down time set to the synthesized ACTION_DOWN event time
+ * until an ACTION_UP is encountered and the state is reset to LISTEN.
+ * <p>
+ * If you are reading this within Java Doc, you are doing something wrong ;)
+ */
+public class PieInputFilter implements IInputFilter {
+ /* WARNING!! The IInputFilter interface is used directly, there is no Binder between this and
+ * the InputDispatcher.
+ * This is fine, because it prevents unnecessary parceling, but beware:
+ * This means we are running on the dispatch or listener thread of the input dispatcher. Every
+ * cycle we waste here adds to the overall input latency.
+ */
+ private static final String TAG = "PieInputFilter";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_INPUT = false;
+ // TODO: Should be turned off in final commit
+ private static final boolean SYSTRACE = true;
+
+ private final Handler mHandler;
+
+ private IInputFilterHost mHost = null; // dispatcher thread
+
+ private static final class MotionEventInfo {
+ private static final int MAX_POOL_SIZE = 16;
+
+ private static final Object sLock = new Object();
+ private static MotionEventInfo sPool;
+ private static int sPoolSize;
+
+ private boolean mInPool;
+
+ public static MotionEventInfo obtain(MotionEvent event, int policyFlags) {
+ synchronized (sLock) {
+ MotionEventInfo info;
+ if (sPoolSize > 0) {
+ sPoolSize--;
+ info = sPool;
+ sPool = info.next;
+ info.next = null;
+ info.mInPool = false;
+ } else {
+ info = new MotionEventInfo();
+ }
+ info.initialize(event, policyFlags);
+ return info;
+ }
+ }
+
+ private void initialize(MotionEvent event, int policyFlags) {
+ this.event = MotionEvent.obtain(event);
+ this.policyFlags = policyFlags;
+ cachedTimeMillis = SystemClock.uptimeMillis();
+ }
+
+ public void recycle() {
+ synchronized (sLock) {
+ if (mInPool) {
+ throw new IllegalStateException("Already recycled.");
+ }
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ sPoolSize++;
+ next = sPool;
+ sPool = this;
+ mInPool = true;
+ }
+ }
+ }
+
+ private void clear() {
+ event.recycle();
+ event = null;
+ policyFlags = 0;
+ }
+
+ public MotionEventInfo next;
+ public MotionEvent event;
+ public int policyFlags;
+ public long cachedTimeMillis;
+ }
+ private final Object mLock = new Object();
+ private MotionEventInfo mMotionEventQueue; // guarded by mLock
+ private MotionEventInfo mMotionEventQueueTail; // guarded by mLock
+ /* DEBUG */
+ private int mMotionEventQueueCountDebug; // guarded by mLock
+
+ private int mDeviceId; // dispatcher only
+ private enum State {
+ LISTEN, DETECTING, LOCKED, SYNTHESIZE, POSTSYNTHESIZE;
+ }
+ private State mState = State.LISTEN; // guarded by mLock
+ private PieGestureTracker mTracker; // guarded by mLock
+ private volatile int mPositions; // written by handler / read by dispatcher
+
+ // only used by dispatcher
+ private long mSyntheticDownTime = -1;
+ private PointerCoords[] mTempPointerCoords = new PointerCoords[1];
+ private PointerProperties[] mTempPointerProperties = new PointerProperties[1];
+
+ public PieInputFilter(Context context, Handler handler) {
+ mHandler = handler;
+
+ final Resources res = context.getResources();
+ mTracker = new PieGestureTracker(res.getDimensionPixelSize(R.dimen.pie_trigger_thickness),
+ res.getDimensionPixelSize(R.dimen.pie_trigger_distance),
+ res.getDimensionPixelSize(R.dimen.pie_perpendicular_distance));
+ mTracker.setOnActivationListener(new OnActivationListener() {
+ public void onActivation(MotionEvent event, int touchX, int touchY, PiePosition position) {
+ mHandler.obtainMessage(PieService.MSG_PIE_ACTIVATION,
+ touchX, touchY, position).sendToTarget();
+ mState = State.LOCKED;
+ }
+ });
+ mTempPointerCoords[0] = new PointerCoords();
+ mTempPointerProperties[0] = new PointerProperties();
+ }
+
+ // called from handler thread (lock taken)
+ public void updateDisplay(Display display, DisplayInfo displayInfo) {
+ synchronized (mLock) {
+ mTracker.updateDisplay(display);
+ }
+ }
+
+ // called from handler thread (lock taken)
+ public void updatePositions(int positions) {
+ mPositions = positions;
+ }
+
+ // called from handler thread
+ public boolean unlockFilter() {
+ synchronized (mLock) {
+ if (mState == State.LOCKED) {
+ mState = State.SYNTHESIZE;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called to enqueue the input event for filtering.
+ * The event must be recycled after the input filter processed it.
+ * This method is guaranteed to be non-reentrant.
+ *
+ * @see InputFilter#filterInputEvent(InputEvent, int)
+ * @param event The input event to enqueue.
+ */
+ // called by the input dispatcher thread
+ public void filterInputEvent(InputEvent event, int policyFlags) throws RemoteException {
+ if (SYSTRACE) {
+ Trace.traceBegin(Trace.TRACE_TAG_INPUT, "filterInputEvent");
+ }
+ try {
+ if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN
+ || !(event instanceof MotionEvent)) {
+ sendInputEvent(event, policyFlags);
+ return;
+ }
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
+ + Integer.toHexString(policyFlags));
+ }
+ MotionEvent motionEvent = (MotionEvent) event;
+ final int deviceId = event.getDeviceId();
+ if (deviceId != mDeviceId) {
+ processDeviceSwitch(deviceId, motionEvent, policyFlags);
+ } else {
+ if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
+ synchronized (mLock) {
+ clearAndResetStateLocked(false, true);
+ }
+ }
+ processMotionEvent(motionEvent, policyFlags);
+ }
+ } finally {
+ event.recycle();
+ if (SYSTRACE) {
+ Trace.traceEnd(Trace.TRACE_TAG_INPUT);
+ }
+ }
+ }
+
+ private void processDeviceSwitch(int deviceId, MotionEvent motionEvent, int policyFlags) {
+ if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mDeviceId = deviceId;
+ synchronized (mLock) {
+ clearAndResetStateLocked(true, false);
+ processMotionEvent(motionEvent, policyFlags);
+ }
+ } else {
+ sendInputEvent(motionEvent, policyFlags);
+ }
+ }
+
+ private void processMotionEvent(MotionEvent motionEvent, int policyFlags) {
+ final int action = motionEvent.getActionMasked();
+
+ synchronized (mLock) {
+ switch (mState) {
+ case LISTEN:
+ if (action == MotionEvent.ACTION_DOWN) {
+ boolean hit = mPositions != 0 && mTracker.start(motionEvent, mPositions);
+ if (DEBUG) Slog.d(TAG, "start:" + hit);
+ if (hit) {
+ // cache the down event
+ cacheDelayedMotionEventLocked(motionEvent, policyFlags);
+ mState = State.DETECTING;
+ return;
+ }
+ }
+ sendInputEvent(motionEvent, policyFlags);
+ break;
+ case DETECTING:
+ cacheDelayedMotionEventLocked(motionEvent, policyFlags);
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (mTracker.move(motionEvent)) {
+ // return: the tracker is either detecting or triggered onActivation
+ return;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "move: reset!");
+ }
+ clearAndResetStateLocked(false, true);
+ break;
+ case LOCKED:
+ cacheDelayedMotionEventLocked(motionEvent, policyFlags);
+ if (action != MotionEvent.ACTION_MOVE) {
+ clearAndResetStateLocked(false, true);
+ }
+ break;
+ case SYNTHESIZE:
+ if (action == MotionEvent.ACTION_MOVE) {
+ clearDelayedMotionEventsLocked();
+ sendSynthesizedMotionEvent(motionEvent, policyFlags);
+ mState = State.POSTSYNTHESIZE;
+ } else {
+ // This is the case where a race condition caught us: We already
+ // returned the handler thread that it is all right to show up the pie
+ // in #gainTouchFocus(), but apparently this was wrong, as the gesture
+ // was canceled now.
+ clearAndResetStateLocked(false, true);
+ }
+ break;
+ case POSTSYNTHESIZE:
+ motionEvent.setDownTime(mSyntheticDownTime);
+ if (action == MotionEvent.ACTION_UP) {
+ mState = State.LISTEN;
+ mSyntheticDownTime = -1;
+ }
+ sendInputEvent(motionEvent, policyFlags);
+ break;
+ }
+ }
+ }
+
+ private void clearAndResetStateLocked(boolean force, boolean shift) {
+ // ignore soft reset in POSTSYNTHESIZE, because we need to tamper with
+ // the event stream and going to LISTEN after an ACTION_UP anyway
+ if (!force && (mState == State.POSTSYNTHESIZE)) {
+ return;
+ }
+ switch (mState) {
+ case LISTEN:
+ // this is a nop
+ break;
+ case DETECTING:
+ mTracker.reset();
+ // intentionally no break here
+ case LOCKED:
+ case SYNTHESIZE:
+ sendDelayedMotionEventsLocked(shift);
+ break;
+ case POSTSYNTHESIZE:
+ // hard reset (this will break the event stream)
+ Slog.w(TAG, "Quit POSTSYNTHESIZE without ACTION_UP from ACTION_DOWN at "
+ + mSyntheticDownTime);
+ mSyntheticDownTime = -1;
+ break;
+ }
+ // if there are future events that need to be tampered with, goto POSTSYNTHESIZE
+ mState = mSyntheticDownTime == -1 ? State.LISTEN : State.POSTSYNTHESIZE;
+ }
+
+ private void sendInputEvent(InputEvent event, int policyFlags) {
+ try {
+ mHost.sendInputEvent(event, policyFlags);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+
+ private void cacheDelayedMotionEventLocked(MotionEvent event, int policyFlags) {
+ MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags);
+ if (mMotionEventQueue == null) {
+ mMotionEventQueue = info;
+ } else {
+ mMotionEventQueueTail.next = info;
+ }
+ mMotionEventQueueTail = info;
+ mMotionEventQueueCountDebug++;
+ if (SYSTRACE) {
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug);
+ }
+ }
+
+ private void sendDelayedMotionEventsLocked(boolean shift) {
+ while (mMotionEventQueue != null) {
+ MotionEventInfo info = mMotionEventQueue;
+ mMotionEventQueue = info.next;
+
+ if (DEBUG) {
+ Slog.d(TAG, "Replay event: " + info.event);
+ }
+ mMotionEventQueueCountDebug--;
+ if (SYSTRACE) {
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug);
+ }
+ if (shift) {
+ final long offset = SystemClock.uptimeMillis() - info.cachedTimeMillis;
+ if (info.event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mSyntheticDownTime = info.event.getDownTime() + offset;
+ }
+ sendMotionEventWithOffset(info.event, info.policyFlags, mSyntheticDownTime, offset);
+ if (info.event.getActionMasked() == MotionEvent.ACTION_UP) {
+ mSyntheticDownTime = -1;
+ }
+ } else {
+ sendInputEvent(info.event, info.policyFlags);
+ }
+ info.recycle();
+ }
+ mMotionEventQueueTail = null;
+ }
+
+ private void clearDelayedMotionEventsLocked() {
+ while (mMotionEventQueue != null) {
+ MotionEventInfo next = mMotionEventQueue.next;
+ mMotionEventQueue.recycle();
+ mMotionEventQueue = next;
+ }
+ mMotionEventQueueTail = null;
+ mMotionEventQueueCountDebug = 0;
+ if (SYSTRACE) {
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, "meq", mMotionEventQueueCountDebug);
+ }
+ }
+
+ private void sendMotionEventWithOffset(MotionEvent event, int policyFlags,
+ long downTime, long offset) {
+ final int pointerCount = event.getPointerCount();
+ PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
+ PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
+ for (int i = 0; i < pointerCount; i++) {
+ event.getPointerCoords(i, coords[i]);
+ event.getPointerProperties(i, properties[i]);
+ }
+ final long eventTime = event.getEventTime() + offset;
+ sendInputEvent(MotionEvent.obtain(downTime, eventTime, event.getAction(), pointerCount,
+ properties, coords, event.getMetaState(), event.getButtonState(), 1.0f, 1.0f,
+ event.getDeviceId(), event.getEdgeFlags(), event.getSource(), event.getFlags()),
+ policyFlags);
+ }
+
+ private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
+ final int oldSize = mTempPointerCoords.length;
+ if (oldSize < size) {
+ PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
+ mTempPointerCoords = new PointerCoords[size];
+ System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
+ }
+ for (int i = oldSize; i < size; i++) {
+ mTempPointerCoords[i] = new PointerCoords();
+ }
+ return mTempPointerCoords;
+ }
+
+ private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
+ final int oldSize = mTempPointerProperties.length;
+ if (oldSize < size) {
+ PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
+ mTempPointerProperties = new PointerProperties[size];
+ System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
+ }
+ for (int i = oldSize; i < size; i++) {
+ mTempPointerProperties[i] = new PointerProperties();
+ }
+ return mTempPointerProperties;
+ }
+
+ private void sendSynthesizedMotionEvent(MotionEvent event, int policyFlags) {
+ if (event.getPointerCount() == 1) {
+ event.getPointerCoords(0, mTempPointerCoords[0]);
+ event.getPointerProperties(0, mTempPointerProperties[0]);
+ MotionEvent down = MotionEvent.obtain(event.getEventTime(), event.getEventTime(),
+ MotionEvent.ACTION_DOWN, 1, mTempPointerProperties, mTempPointerCoords,
+ event.getMetaState(), event.getButtonState(),
+ 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
+ event.getSource(), event.getFlags());
+ Slog.d(TAG, "Synthesized event:" + down);
+ sendInputEvent(down, policyFlags);
+ mSyntheticDownTime = event.getEventTime();
+ } else {
+ Slog.w(TAG, "Could not synthesize MotionEvent, this will drop all following events!");
+ }
+ }
+
+ // should never be called
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ // called by the input dispatcher thread
+ public void install(IInputFilterHost host) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Pie input filter installed.");
+ }
+ mHost = host;
+ synchronized (mLock) {
+ clearAndResetStateLocked(true, false);
+ }
+ }
+
+ // called by the input dispatcher thread
+ public void uninstall() throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Pie input filter uninstalled.");
+ }
+ }
+
+ // called by a Binder thread
+ public void dump(PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.print(prefix);
+ pw.println("mState=" + mState.name());
+ pw.print(prefix);
+ pw.println("mPositions=0x" + Integer.toHexString(mPositions));
+ pw.print(prefix);
+ pw.println("mQueue=" + mMotionEventQueueCountDebug + " items");
+ }
+ }
+}
diff --git a/services/java/com/android/server/pie/PieService.java b/services/java/com/android/server/pie/PieService.java
new file mode 100644
index 0000000..52cfff4
--- /dev/null
+++ b/services/java/com/android/server/pie/PieService.java
@@ -0,0 +1,469 @@
+/*
+ * 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.pie;
+
+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.IBinder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.service.pie.IPieActivationListener;
+import android.service.pie.IPieHostCallback;
+import android.service.pie.IPieService;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.util.pie.PiePosition;
+import com.android.server.input.InputManagerService;
+import com.android.server.wm.WindowManagerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A system service to track and handle pie activations gestures. This service interacts with
+ * the {@link InputManagerService} to do all the dirty work for pie controls:
+ * <li>Installing an input filter and listen for pie activation gestures</li>
+ * <li>Removing those gestures from the input stream</li>
+ * <li>Transferring touch focus to the pie controls when shown</li>
+ */
+public class PieService extends IPieService.Stub {
+ public static final String TAG = "PieService";
+ public static final boolean DEBUG = false;
+ public static final boolean DEBUG_INPUT = false;
+
+ public static final int MSG_PIE_ACTIVATION = 32023;
+ public static final int MSG_PIE_DEACTIVATION = 32024;
+ public static final int MSG_UPDATE_POSITIONS = 32025;
+
+ private final Context mContext;
+ private final InputManagerService mInputManager;
+ private final WindowManagerService mWindowManager;
+
+ private HandlerThread mHandlerThread = new HandlerThread("Pie");
+
+ // Lock for thread, handler, mInputFilter, activations and listener related variables
+ private final Object mLock = new Object();
+ private Handler mHandler;
+ private PieInputFilter mInputFilter;
+
+ private int mGlobalPositions = 0;
+ private boolean mIsMonitoring = false;
+
+ private final class PieActivationListenerRecord extends IPieHostCallback.Stub implements DeathRecipient {
+ private boolean mActive;
+
+ public PieActivationListenerRecord(IPieActivationListener listener) {
+ this.listener = listener;
+ this.positions = 0;
+ }
+
+ public void binderDied() {
+ removeListenerRecord(this);
+ }
+
+ public void updatePositions(int positions) {
+ this.positions = positions;
+ }
+
+ public boolean eligibleForActivation(int positionFlag) {
+ return (positions & positionFlag) != 0;
+ }
+
+ public boolean notifyPieActivation(int touchX, int touchY, PiePosition position) {
+ if ((positions & position.FLAG) != 0) {
+ try {
+ listener.onPieActivation(touchX, touchY, position.INDEX, 0);
+ mActive = true;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify process, assuming it died.", e);
+ 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;
+ }
+
+ // called through Binder
+ public void restoreListenerState() throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Restore listener state");
+ }
+ if (mActive) {
+ mWindowManager.resetStatusBarVisibilityMask();
+ mInputFilter.unlockFilter();
+ mActive = false;
+ synchronized (mLock) {
+ mActiveRecord = null;
+ mHandler.obtainMessage(MSG_PIE_DEACTIVATION, mGlobalPositions, 0).sendToTarget();
+ }
+ }
+ }
+
+ 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 final IPieActivationListener listener;
+ }
+ private final List<PieActivationListenerRecord> mPieActivationListener =
+ new ArrayList<PieActivationListenerRecord>();
+ private PieActivationListenerRecord mActiveRecord = null;
+ // end of lock guarded variables
+
+ private DisplayObserver mDisplayObserver;
+
+ // called by system server
+ public PieService(Context context, WindowManagerService windowManager, InputManagerService inputManager) {
+ mContext = context;
+ mInputManager = inputManager;
+ mWindowManager = windowManager;
+ }
+
+ // called by system server
+ public void systemReady() {
+ if (DEBUG) Slog.d(TAG, "Starting the pie 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.obtainMessage(MSG_UPDATE_POSITIONS, mGlobalPositions, 0).sendToTarget();
+ updateMonitoring();
+ }
+
+ private void enforceMonitoringLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "Attempting to start monitoring input events ...");
+ }
+ if (mInputFilter == null) {
+ mInputFilter = new PieInputFilter(mContext, mHandler);
+ mInputManager.registerSecondaryInputFilter(mInputFilter);
+ }
+ mDisplayObserver.observe();
+ }
+
+ private void shutdownMonitoringLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "Shutting down monitoring input events ...");
+ }
+ mDisplayObserver.unobserve();
+ if (mInputFilter != null) {
+ mInputManager.unregisterSecondaryInputFilter(mInputFilter);
+ mInputFilter = null;
+ }
+ }
+
+ private void updateMonitoring() {
+ synchronized(mLock) {
+ if (!mIsMonitoring && mGlobalPositions != 0) {
+ enforceMonitoringLocked();
+ } else if (mIsMonitoring && mGlobalPositions == 0) {
+ shutdownMonitoringLocked();
+ }
+ mIsMonitoring = mGlobalPositions != 0;
+ }
+ }
+
+ // called through Binder
+ public IPieHostCallback registerPieActivationListener(IPieActivationListener 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");
+ }
+
+ PieActivationListenerRecord record = null;
+ synchronized(mLock) {
+ record = findListenerRecordLocked(listener.asBinder());
+ if (record == null) {
+ record = new PieActivationListenerRecord(listener);
+ mPieActivationListener.add(record);
+ }
+ }
+ return record;
+ }
+
+ // called through Binder
+ public void updatePieActivationListener(IBinder listener, int positionFlags) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ synchronized(mLock) {
+ PieActivationListenerRecord record = findListenerRecordLocked(listener);
+ if (record == null) {
+ Slog.w(TAG, "Unknown listener on update listener. Register first?");
+ throw new IllegalStateException("listener not registered");
+ }
+ record.updatePositions(positionFlags);
+ updatePositionsLocked();
+ if (mActiveRecord == null && mHandler != null) {
+ mHandler.obtainMessage(MSG_UPDATE_POSITIONS, mGlobalPositions, 0).sendToTarget();
+ }
+ }
+ updateMonitoring();
+ }
+
+ private PieActivationListenerRecord findListenerRecordLocked(IBinder listener) {
+ for (PieActivationListenerRecord record : mPieActivationListener) {
+ if (record.listener.asBinder().equals(listener)) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ private void updatePositionsLocked() {
+ mGlobalPositions = 0;
+ for (PieActivationListenerRecord temp : mPieActivationListener) {
+ mGlobalPositions |= temp.positions;
+ }
+ }
+
+ private void removeListenerRecord(PieActivationListenerRecord record) {
+ synchronized(mLock) {
+ mPieActivationListener.remove(record);
+ updatePositionsLocked();
+ // check if the record was the active one
+ if (record == mActiveRecord) {
+ mHandler.obtainMessage(MSG_PIE_DEACTIVATION, mGlobalPositions, 0).sendToTarget();
+ }
+ }
+ updateMonitoring();
+ }
+
+ // called by handler thread
+ private boolean propagateActivation(int touchX, int touchY, PiePosition position) {
+ if (mActiveRecord != null) {
+ Slog.w(TAG, "Handing activition while another activition is still in progress");
+ }
+ if (!mWindowManager.updateStatusBarVisibilityMask(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)) {
+ return false;
+ }
+ synchronized(mLock) {
+ PieActivationListenerRecord target = null;
+ for (PieActivationListenerRecord record : mPieActivationListener) {
+ if (record.eligibleForActivation(position.FLAG)) {
+ target = record;
+ break;
+ }
+ }
+ // NOTE: We can do this here because the #onPieActivation() 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.notifyPieActivation(touchX, touchY, position)) {
+ mActiveRecord = target;
+ }
+ }
+ if (mActiveRecord != null) {
+ mWindowManager.reevaluateStatusBarVisibility();
+ } else {
+ mWindowManager.resetStatusBarVisibilityMask();
+ }
+ return mActiveRecord != null;
+ }
+
+ @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, "PieService 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 PieService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("PIE SERVICE (dumpsys pieservice)\n");
+ synchronized(mLock) {
+ pw.println(" mIsMonitoring=" + mIsMonitoring);
+ pw.println(" mInputFilter=" + mInputFilter);
+ if (mInputFilter != null) {
+ mInputFilter.dump(pw, " ");
+ }
+ pw.println(" mGlobalPositions=0x" + Integer.toHexString(mGlobalPositions));
+ int i = 0;
+ for (PieActivationListenerRecord record : mPieActivationListener) {
+ if (record == mActiveRecord) break;
+ i++;
+ }
+ pw.println(" mActiveRecord=" + (mActiveRecord != null ? ("#" + i) : "null"));
+ i = 0;
+ for (PieActivationListenerRecord record : mPieActivationListener) {
+ pw.println(" Listener #" + i + ":");
+ record.dump(pw, " ");
+ i++;
+ }
+ }
+ }
+
+ private final class H extends Handler {
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case MSG_PIE_ACTIVATION:
+ if (DEBUG) {
+ Slog.d(TAG, "Activating pie on " + m.obj.toString());
+ }
+ // Since input filter runs asynchronously to us, double activation may happen
+ // theoretically. Take the safe route here.
+ removeMessages(MSG_PIE_ACTIVATION);
+ if (propagateActivation(m.arg1, m.arg2, (PiePosition) m.obj)) {
+ // switch off all positions for the time of activation
+ updatePositionsHandler(0);
+ }
+ break;
+ case MSG_PIE_DEACTIVATION:
+ if (DEBUG) {
+ Slog.d(TAG, "Deactivating pie with positions 0x" + Integer.toHexString(m.arg1));
+ }
+ // switch back on the positions we need
+ updatePositionsHandler(m.arg1);
+ break;
+ case MSG_UPDATE_POSITIONS:
+ if (DEBUG) {
+ Slog.d(TAG, "Updating positions 0x" + Integer.toHexString(m.arg1));
+ }
+ updatePositionsHandler(m.arg1);
+ }
+ }
+
+ private void updatePositionsHandler(int positions) {
+ synchronized (mLock) {
+ if (mInputFilter != null) {
+ mInputFilter.updatePositions(positions);
+ }
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 4133bbd..a385a7a 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -487,6 +487,12 @@ public class WindowManagerService extends IWindowManager.Stub
int mLastStatusBarVisibility = 0;
+ /**
+ * Mask used to control the visibility of the status and navigation bar for short periods
+ * of time. (e.g. during pie controls)
+ */
+ int mStatusBarVisibilityMask = 0;
+
// State while inside of layoutAndPlaceSurfacesLocked().
boolean mFocusMayChange;
@@ -10394,6 +10400,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
mLastStatusBarVisibility = visibility;
visibility = mPolicy.adjustSystemUiVisibilityLw(visibility);
+ visibility &= ~mStatusBarVisibilityMask;
updateStatusBarVisibilityLocked(visibility);
}
}
@@ -10432,6 +10439,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void reevaluateStatusBarVisibility() {
synchronized (mWindowMap) {
int visibility = mPolicy.adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
+ visibility &= ~mStatusBarVisibilityMask;
updateStatusBarVisibilityLocked(visibility);
performLayoutAndPlaceSurfacesLocked();
}
@@ -10526,6 +10534,31 @@ public class WindowManagerService extends IWindowManager.Stub
Binder.restoreCallingIdentity(origId);
}
+ /**
+ * Tries to set the status bar visibilty mask. This will fail if the mask was set already.
+ *
+ * @param mask specifies the positive mask. E.g. all bit that should be masked out are set.
+ */
+ public boolean updateStatusBarVisibilityMask(int mask) {
+ boolean result = false;
+ synchronized(mWindowMap) {
+ if (mStatusBarVisibilityMask == 0) {
+ mStatusBarVisibilityMask = mask;
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Call this, only if {@link #updateStatusBarVisibilityMask(int)} returned {@code true}.
+ */
+ public void resetStatusBarVisibilityMask() {
+ synchronized(mWindowMap) {
+ mStatusBarVisibilityMask = 0;
+ }
+ }
+
void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
mPolicy.dump(" ", pw, args);