summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk3
-rw-r--r--core/java/android/content/res/Configuration.java8
-rw-r--r--core/java/android/service/pie/IPieActivationListener.aidl14
-rw-r--r--core/java/android/service/pie/IPieHostCallback.aidl15
-rw-r--r--core/java/android/service/pie/IPieService.aidl20
-rw-r--r--core/java/android/service/pie/PieManager.java193
-rw-r--r--core/java/android/service/pie/package.html5
-rw-r--r--core/java/com/android/internal/util/pie/PiePosition.java42
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/dimens.xml11
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java309
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pie/PieItem.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSliceContainer.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSysInfo.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pie/PieView.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pie/PieLayout.java)90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PieController.java318
-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
26 files changed, 1953 insertions, 570 deletions
diff --git a/Android.mk b/Android.mk
index 28036cf..d1e7d90 100644
--- a/Android.mk
+++ b/Android.mk
@@ -145,6 +145,9 @@ LOCAL_SRC_FILES += \
core/java/android/os/IVibratorService.aidl \
core/java/android/service/dreams/IDreamManager.aidl \
core/java/android/service/dreams/IDreamService.aidl \
+ core/java/android/service/pie/IPieService.aidl \
+ core/java/android/service/pie/IPieActivationListener.aidl \
+ core/java/android/service/pie/IPieHostCallback.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
core/java/android/service/wallpaper/IWallpaperService.aidl \
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 3a6d307..19d7277 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -884,11 +884,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
screenHeightDp = delta.screenHeightDp;
}
- if (delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
- changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ if (delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && smallestScreenWidthDp != delta.smallestScreenWidthDp) {
+ changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
smallestScreenWidthDp = delta.smallestScreenWidthDp;
}
- if (delta.densityDpi != DENSITY_DPI_UNDEFINED) {
+ if (delta.densityDpi != DENSITY_DPI_UNDEFINED &&
+ densityDpi != delta.densityDpi) {
changed |= ActivityInfo.CONFIG_DENSITY;
densityDpi = delta.densityDpi;
}
diff --git a/core/java/android/service/pie/IPieActivationListener.aidl b/core/java/android/service/pie/IPieActivationListener.aidl
new file mode 100644
index 0000000..f05e472
--- /dev/null
+++ b/core/java/android/service/pie/IPieActivationListener.aidl
@@ -0,0 +1,14 @@
+
+package android.service.pie;
+
+import android.view.InputEvent;
+
+/** @hide */
+interface IPieActivationListener {
+
+ /** Called when a gesture is detected that fits to the pie activation gesture. At this point in
+ * time gesture detection is disabled. Call IPieHostCallback.restoreState() to
+ * recover from this.
+ */
+ oneway void onPieActivation(int touchX, int touchY, int positionIndex, int flags);
+} \ No newline at end of file
diff --git a/core/java/android/service/pie/IPieHostCallback.aidl b/core/java/android/service/pie/IPieHostCallback.aidl
new file mode 100644
index 0000000..1853294
--- /dev/null
+++ b/core/java/android/service/pie/IPieHostCallback.aidl
@@ -0,0 +1,15 @@
+package android.service.pie;
+
+/** @hide */
+interface IPieHostCallback {
+
+ /** After being activated, this allows the pie control to steal focus from the current
+ * window
+ */
+ boolean gainTouchFocus(IBinder windowToken);
+
+ /** Turns listening for pie activation gestures on again, after it was disabled during
+ * the call to the listener.
+ */
+ oneway void restoreListenerState();
+} \ No newline at end of file
diff --git a/core/java/android/service/pie/IPieService.aidl b/core/java/android/service/pie/IPieService.aidl
new file mode 100644
index 0000000..4fd1a0f
--- /dev/null
+++ b/core/java/android/service/pie/IPieService.aidl
@@ -0,0 +1,20 @@
+package android.service.pie;
+
+import android.service.pie.IPieActivationListener;
+import android.service.pie.IPieHostCallback;
+
+/** @hide */
+interface IPieService {
+
+ /** Register a listener for pie activation gestures. Initially the listener
+ * is set to listen for no position. Use updatePieActivationListener() to
+ * bind the listener to positions.
+ * Use the returned IPieHostCallback to manipulate the state after activation.
+ */
+ IPieHostCallback registerPieActivationListener(in IPieActivationListener listener);
+
+ /** Update the listener to react on gestures in the given positions.
+ */
+ void updatePieActivationListener(in IBinder listener, int positionFlags);
+
+} \ No newline at end of file
diff --git a/core/java/android/service/pie/PieManager.java b/core/java/android/service/pie/PieManager.java
new file mode 100644
index 0000000..1ba9d37
--- /dev/null
+++ b/core/java/android/service/pie/PieManager.java
@@ -0,0 +1,193 @@
+/*
+ * 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 android.service.pie;
+
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.pie.IPieService;
+import android.util.Slog;
+
+import com.android.internal.util.pie.PiePosition;
+
+/**
+ * This is a simple Manager class for pie service on the application side. The application need
+ * {@code INJECT_EVENTS} permission to register {@code PieActivationListener}s.<br>
+ * See {@link IPieService} for more information.
+ *
+ * @see IPieService
+ * @hide
+ */
+public class PieManager {
+ public static final String TAG = "PieManager";
+ public static final boolean DEBUG = false;
+
+ private static PieManager sInstance;
+
+ private final IPieService mPs;
+
+ public static abstract class PieActivationListener {
+ private Handler mHandler;
+ private IPieHostCallback mCallback;
+
+ private class Delegator extends IPieActivationListener.Stub {
+ public void onPieActivation(final int touchX, final int touchY, final int positionIndex, final int flags)
+ throws RemoteException {
+ mHandler.post(new Runnable() {
+ public void run() {
+ PieActivationListener.this.onPieActivation(touchX, touchY, PiePosition.values()[positionIndex], flags);
+ }
+ });
+ }
+ }
+ private Delegator mDelegator;
+
+ public PieActivationListener() {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ public PieActivationListener(Looper looper) {
+ mHandler = new Handler(looper);
+ mDelegator = new Delegator();
+ }
+
+ /* package */ void setHostCallback(IPieHostCallback hostCallback) {
+ mCallback = hostCallback;
+ }
+
+ /**
+ * Override this to receive activations from the pie service.
+ *
+ * @param touchX the last X position a touch event was registered.
+ * @param touchY the last Y position a touch event was registered.
+ * @param position the position of the activation.
+ * @param flags currently 0.
+ * @see IPieActivationListener#onPieActivation(int, int, int, int)
+ */
+ public abstract void onPieActivation(int touchX, int touchY, PiePosition position, int flags);
+
+ /**
+ * After being activated, this allows the pie control to steal focus from the current
+ * window.
+ *
+ * @see IPieHostCallback#gainTouchFocus(IBinder)
+ */
+ public boolean gainTouchFocus(IBinder applicationWindowToken) {
+ try {
+ return mCallback.gainTouchFocus(applicationWindowToken);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "gainTouchFocus failed: " + e.getMessage());
+ /* fall through */
+ }
+ return false;
+ }
+
+ /**
+ * Turns listening for pie activation gestures on again, after it was disabled during
+ * the call to the listener.
+ *
+ * @see IPieHostCallback#restoreListenerState()
+ */
+ public void restoreListenerState() {
+ if (DEBUG) {
+ Slog.d(TAG, "restore listener state: " + Thread.currentThread().getName());
+ }
+ try {
+ mCallback.restoreListenerState();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "restoreListenerState failed: " + e.getMessage());
+ /* fall through */
+ }
+ }
+ }
+
+ private PieManager(IPieService ps) {
+ mPs = ps;
+ }
+
+ /**
+ * Gets an instance of the pie manager.
+ *
+ * @return The pie manager instance.
+ * @hide
+ */
+ public static PieManager getInstance() {
+ synchronized (PieManager.class) {
+ if (sInstance == null) {
+ IBinder b = ServiceManager.getService("pieservice");
+ sInstance = new PieManager(IPieService.Stub.asInterface(b));
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Checks if the pie service is present.
+ * <p>
+ * Since the service is only started at boot time and is bound to the system server, this
+ * is constant for the devices up time.
+ *
+ * @return {@code true} when the pie service is running on this device.
+ * @hide
+ */
+ public boolean isPresent() {
+ return mPs != null;
+ }
+
+ /**
+ * Register a listener for pie activation gestures. Initially the listener
+ * is set to listen for no position. Use updatePieActivationListener() to
+ * bind the listener to positions.
+ *
+ * @param listener is the activation listener.
+ * @return {@code true} if the registration was successful.
+ * @hide
+ */
+ public boolean setPieActivationListener(PieActivationListener listener) {
+ if (DEBUG) {
+ Slog.d(TAG, "Set pie activation listener");
+ }
+ try {
+ IPieHostCallback callback = mPs.registerPieActivationListener(listener.mDelegator);
+ listener.setHostCallback(callback);
+ return true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set pie activation listener: " + e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Update the listener to react on gestures in the given positions.
+ *
+ * @param listener is a already registered listener.
+ * @param positions is a bit mask describing the positions to listen to.
+ * @hide
+ */
+ public void updatePieActivationListener(PieActivationListener listener, int positions) {
+ if (DEBUG) {
+ Slog.d(TAG, "Update pie activation listener: 0x" + Integer.toHexString(positions));
+ }
+ try {
+ mPs.updatePieActivationListener(listener.mDelegator.asBinder(), positions);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to update pie activation listener: " + e.getMessage());
+ }
+ }
+
+}
diff --git a/core/java/android/service/pie/package.html b/core/java/android/service/pie/package.html
new file mode 100644
index 0000000..c9f96a6
--- /dev/null
+++ b/core/java/android/service/pie/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/core/java/com/android/internal/util/pie/PiePosition.java b/core/java/com/android/internal/util/pie/PiePosition.java
new file mode 100644
index 0000000..be9626f
--- /dev/null
+++ b/core/java/com/android/internal/util/pie/PiePosition.java
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.util.pie;
+
+/**
+ * Defines the positions in which pie controls may appear and gestures may be recognized by the
+ * pie service.
+ * This defines an index and an flag for each position.
+ */
+public enum PiePosition {
+ LEFT(0, 0),
+ BOTTOM(1, 1),
+ RIGHT(2, 1),
+ TOP(3, 0);
+
+ PiePosition(int index, int factor) {
+ INDEX = index;
+ FLAG = (0x01<<index);
+ FACTOR = factor;
+ }
+
+ public final int INDEX;
+ public final int FLAG;
+ /**
+ * This is 1 when the position is not at the axis (like {@link PiePosition.RIGHT} is
+ * at {@code Layout.getWidth()} not at {@code 0}).
+ */
+ public final int FACTOR;
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1309a65..ad5eb13 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1141,4 +1141,7 @@
<!-- Integer to configure panel auto brightness mode when changed -->
<integer name="config_panelAutoBrightnessValue">-1</integer>
+ <!-- True if the pie service should be started at system start. This is must be true
+ to support pie controls. -->
+ <bool name="config_allowPieService">true</bool>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 784a2cd..800d0d1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -349,4 +349,15 @@
<!-- Left padding of num pad key on keyguard -->
<dimen name="kg_num_pad_key_padding_left">10dp</dimen>
+ <!-- Pie service: Distance a swipe must travel to be recognized as pie swipe.
+ Used by the pie service. -->
+ <dimen name="pie_trigger_distance">10dp</dimen>
+
+ <!-- Pie service: This is the distance a swipe can travel orthogonally to its actual swipe
+ direction to be still recognized as pie swipe.
+ Used by the pie service. -->
+ <dimen name="pie_perpendicular_distance">15dp</dimen>
+
+ <!-- Pie service: Thickness of the active trigger fields around the screen border -->
+ <dimen name="pie_trigger_thickness">6dp</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f0b00ff..f0f83a0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1993,4 +1993,10 @@
<java-symbol type="string" name="symbol_picker_Y" />
<java-symbol type="string" name="symbol_picker_z" />
<java-symbol type="string" name="symbol_picker_Z" />
+
+ <!-- Pie Controls -->
+ <java-symbol type="bool" name="config_allowPieService" />
+ <java-symbol type="dimen" name="pie_trigger_thickness" />
+ <java-symbol type="dimen" name="pie_trigger_distance" />
+ <java-symbol type="dimen" name="pie_perpendicular_distance" />
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6881073..158c355 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -218,10 +218,6 @@
<dimen name="status_bar_battery_cluster_text_margin">-1dip</dimen>
<!-- ==================== pie controls ==================== -->
- <!-- Actual width/height of the trigger views placed on the UI -->
- <dimen name="pie_trigger_height">3dp</dimen>
- <!-- The distance a touch event must travel on the surface to trigger the pie control -->
- <dimen name="pie_trigger_distance">8dp</dimen>
<!-- Padding between the pie controls and the screen border -->
<dimen name="pie_padding">16dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 7799ce9..bedc68d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -28,10 +28,8 @@ import com.android.systemui.SystemUI;
import com.android.systemui.recent.RecentTasksLoader;
import com.android.systemui.recent.RecentsActivity;
import com.android.systemui.recent.TaskDescription;
-import com.android.systemui.statusbar.pie.PieLayout;
import com.android.systemui.statusbar.policy.NotificationRowLayout;
import com.android.systemui.statusbar.policy.PieController;
-import com.android.systemui.statusbar.policy.PieController.Position;
import com.android.systemui.statusbar.tablet.StatusBarPanel;
import android.app.ActivityManager;
@@ -46,7 +44,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
@@ -54,7 +51,6 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Paint;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
@@ -65,6 +61,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.pie.PieManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -92,7 +89,6 @@ public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks {
public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
- public static final boolean DEBUG_INPUT = false;
public static final boolean MULTIUSER_DEBUG = false;
protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
@@ -134,7 +130,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected FrameLayout mStatusBarContainer;
-
/**
* An interface for navigation key bars to allow status bars to signal which keys are
* currently of interest to the user.<br>
@@ -162,95 +157,7 @@ public abstract class BaseStatusBar extends SystemUI implements
new ArrayList<NavigationBarCallback>();
// Pie Control
- protected int mExpandedDesktopState;
protected PieController mPieController;
- protected PieLayout mPieContainer;
- private int mPieTriggerSlots;
- private int mPieTriggerMask = Position.LEFT.FLAG
- | Position.BOTTOM.FLAG
- | Position.RIGHT.FLAG
- | Position.TOP.FLAG;
- private View[] mPieTrigger = new View[Position.values().length];
- private PieSettingsObserver mSettingsObserver;
-
- private View.OnTouchListener mPieTriggerOnTouchHandler = new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- final int action = event.getAction();
- final PieController.Tracker tracker = (PieController.Tracker)v.getTag();
-
- if (tracker == null) {
- if (DEBUG_INPUT) {
- Slog.v(TAG, "Pie trigger onTouch: action: " + action + ", ("
- + event.getAxisValue(MotionEvent.AXIS_X) + ","
- + event.getAxisValue(MotionEvent.AXIS_Y) + ") position: NULL returning: false");
- }
- return false;
- }
-
- if (!mPieController.isShowing()) {
- if (event.getPointerCount() > 1) {
- if (DEBUG_INPUT) {
- Slog.v(TAG, "Pie trigger onTouch: action: " + action
- + ", (to many pointers) position: " + tracker.position.name()
- + " returning: false");
- }
- return false;
- }
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- tracker.start(event);
- break;
- case MotionEvent.ACTION_MOVE:
- if (tracker.move(event)) {
- if (DEBUG) {
- Slog.v(TAG, "Pie control activated on: ("
- + event.getAxisValue(MotionEvent.AXIS_X) + ","
- + event.getAxisValue(MotionEvent.AXIS_Y) + ") with position: "
- + tracker.position.name());
- }
- if (tracker.position == Position.BOTTOM
- && mPieController.isSearchLightEnabled()) {
- // if we are at the bottom and nothing else is there, use a
- // search light!
- showSearchPanel();
- } else {
- // set the snap points depending on current trigger and mask
- mPieContainer.setSnapPoints(mPieTriggerMask & ~mPieTriggerSlots);
- // send the activation to the controller
- mPieController.activateFromTrigger(v, event, tracker.position);
- // forward a spoofed ACTION_DOWN event
- MotionEvent echo = event.copy();
- echo.setAction(MotionEvent.ACTION_DOWN);
- return mPieContainer.onTouch(v, echo);
- }
- }
- break;
- default:
- // whatever it was, we are giving up on this one
- tracker.active = false;
- break;
- }
- } else {
- if (DEBUG_INPUT) {
- Slog.v(TAG, "Pie trigger onTouch: action: " + action + ", ("
- + event.getAxisValue(MotionEvent.AXIS_X) + ","
- + event.getAxisValue(MotionEvent.AXIS_Y)
- + ") position: " + tracker.position.name() + " delegating");
- }
- return mPieContainer.onTouch(v, event);
- }
- if (DEBUG_INPUT) {
- Slog.v(TAG, "Pie trigger onTouch: action: " + action + ", ("
- + event.getAxisValue(MotionEvent.AXIS_X) + ","
- + event.getAxisValue(MotionEvent.AXIS_Y) + ") position: "
- + tracker.position.name() + " returning: " + tracker.active);
- }
- return tracker.active;
- }
-
- };
// UI-specific methods
@@ -411,11 +318,11 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}, filter);
- mSettingsObserver = new PieSettingsObserver(new Handler());
-
- // this calls attachPie() implicitly
- mSettingsObserver.onChange(true);
- mSettingsObserver.observe();
+ if (PieManager.getInstance().isPresent()) {
+ mPieController = new PieController(mContext);
+ mPieController.attachStatusBar(this);
+ addNavigationBarCallback(mPieController);
+ }
}
public void userSwitched(int newUserId) {
@@ -803,30 +710,12 @@ public abstract class BaseStatusBar extends SystemUI implements
if (DEBUG) Slog.d(TAG, "opening search panel");
if (mSearchPanelView != null) {
mSearchPanelView.show(true, true);
-
- View bottom = mPieTrigger[Position.BOTTOM.INDEX];
- if (bottom != null) {
- WindowManager.LayoutParams lp =
- (android.view.WindowManager.LayoutParams) bottom.getLayoutParams();
- lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
- mWindowManager.updateViewLayout(bottom, lp);
- }
}
break;
case MSG_CLOSE_SEARCH_PANEL:
if (DEBUG) Slog.d(TAG, "closing search panel");
if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
mSearchPanelView.show(false, true);
-
- View bottom = mPieTrigger[Position.BOTTOM.INDEX];
- if (bottom != null) {
- WindowManager.LayoutParams lp =
- (android.view.WindowManager.LayoutParams) bottom.getLayoutParams();
- lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
- mWindowManager.updateViewLayout(bottom, lp);
- }
}
break;
}
@@ -1304,6 +1193,16 @@ public abstract class BaseStatusBar extends SystemUI implements
return km.inKeyguardRestrictedInputMode();
}
+ public int getExpandedDesktopMode() {
+ ContentResolver resolver = mContext.getContentResolver();
+ boolean expanded = Settings.System.getInt(resolver,
+ Settings.System.EXPANDED_DESKTOP_STATE, 0) == 1;
+ if (expanded) {
+ return Settings.System.getInt(resolver, Settings.System.EXPANDED_DESKTOP_STYLE, 0);
+ }
+ return 0;
+ }
+
public void addNavigationBarCallback(NavigationBarCallback callback) {
mNavigationCallbacks.add(callback);
}
@@ -1328,181 +1227,9 @@ public abstract class BaseStatusBar extends SystemUI implements
// Pie Controls
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- if (DEBUG) Slog.d(TAG, "Configuration changed! Update pie triggers");
-
- attachPie();
- }
-
- private final class PieSettingsObserver extends ContentObserver {
- PieSettingsObserver(Handler handler) {
- super(handler);
- }
-
- void observe() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.PIE_CONTROLS), false, this);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.PIE_POSITIONS), false, this);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.EXPANDED_DESKTOP_STATE), false, this);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.EXPANDED_DESKTOP_STYLE), false, this);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- ContentResolver resolver = mContext.getContentResolver();
-
- mPieTriggerSlots = Settings.System.getInt(resolver,
- Settings.System.PIE_POSITIONS, Position.BOTTOM.FLAG);
-
- boolean expanded = Settings.System.getInt(resolver,
- Settings.System.EXPANDED_DESKTOP_STATE, 0) == 1;
- if (expanded) {
- mExpandedDesktopState = Settings.System.getInt(resolver,
- Settings.System.EXPANDED_DESKTOP_STYLE, 0);
- } else {
- mExpandedDesktopState = 0;
- }
-
- attachPie();
- }
- }
-
- private boolean isPieEnabled() {
- int pie = Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.PIE_CONTROLS, 0);
-
- return (pie == 1 && mExpandedDesktopState != 0) || pie == 2;
- }
-
- private void attachPie() {
- if (isPieEnabled()) {
-
- // Create our container, if it does not exist already
- if (mPieContainer == null) {
- mPieContainer = new PieLayout(mContext);
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
- PixelFormat.TRANSLUCENT);
- // This title is for debugging only. See: dumpsys window
- lp.setTitle("PieControlPanel");
- lp.windowAnimations = android.R.style.Animation;
- lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_BEHIND;
-
- mWindowManager.addView(mPieContainer, lp);
- // once we need a pie controller, we create one and keep it forever ...
- if (mPieController == null) {
- mPieController = new PieController(mContext);
- mPieController.attachStatusBar(this);
- addNavigationBarCallback(mPieController);
- }
- mPieController.attachContainer(mPieContainer);
- }
-
- // add or update pie triggers
- if (DEBUG) {
- Slog.d(TAG, "AttachPie with trigger position flags: "
- + mPieTriggerSlots + " masked: " + (mPieTriggerSlots & mPieTriggerMask));
- }
-
- refreshPieTriggers();
-
- } else {
- for (int i = 0; i < mPieTrigger.length; i++) {
- if (mPieTrigger[i] != null) {
- mWindowManager.removeView(mPieTrigger[i]);
- mPieTrigger[i] = null;
- }
- }
- // detach from the pie container and unregister observers and receivers
- if (mPieController != null) {
- mPieController.detachContainer();
- mPieContainer = null;
- }
- }
- }
-
public void updatePieTriggerMask(int newMask) {
- int oldState = mPieTriggerSlots & mPieTriggerMask;
- mPieTriggerMask = newMask;
-
- // first we check, if it would make a change
- if ((mPieTriggerSlots & mPieTriggerMask) != oldState) {
- if (isPieEnabled()) {
- refreshPieTriggers();
- }
- }
- }
-
- // This should only be called, when is is clear that the pie controls are active
- private void refreshPieTriggers() {
- for (Position g : Position.values()) {
- View trigger = mPieTrigger[g.INDEX];
- if (trigger == null && (mPieTriggerSlots & mPieTriggerMask & g.FLAG) != 0) {
- trigger = new View(mContext);
- trigger.setClickable(false);
- trigger.setLongClickable(false);
- trigger.setTag(mPieController.buildTracker(g));
- trigger.setOnTouchListener(mPieTriggerOnTouchHandler);
-
- if (DEBUG) {
- trigger.setVisibility(View.VISIBLE);
- trigger.setBackgroundColor(0x77ff0000);
- Slog.d(TAG, "addPieTrigger on " + g.INDEX
- + " with position: " + g + " : " + trigger.toString());
- }
- mWindowManager.addView(trigger, getPieTriggerLayoutParams(g));
- mPieTrigger[g.INDEX] = trigger;
- } else if (trigger != null && (mPieTriggerSlots & mPieTriggerMask & g.FLAG) == 0) {
- mWindowManager.removeView(trigger);
- mPieTrigger[g.INDEX] = null;
- } else if (trigger != null) {
- mWindowManager.updateViewLayout(trigger, getPieTriggerLayoutParams(g));
- }
+ if (mPieController != null) {
+ mPieController.updatePieTriggerMask(newMask);
}
}
-
- private WindowManager.LayoutParams getPieTriggerLayoutParams(Position position) {
- final Resources res = mContext.getResources();
-
- int width = (int) (res.getDisplayMetrics().widthPixels * 0.8f);
- int height = (int) (res.getDisplayMetrics().heightPixels * 0.8f);
- int triggerThickness = (int) (res.getDimensionPixelSize(R.dimen.pie_trigger_height));
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- (position == Position.TOP || position == Position.BOTTOM
- ? width : triggerThickness),
- (position == Position.LEFT || position == Position.RIGHT
- ? height : triggerThickness),
- WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
- /* | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM */,
- PixelFormat.TRANSLUCENT);
- // This title is for debugging only. See: dumpsys window
- lp.setTitle("PieTrigger" + position.name());
- if (position == Position.LEFT || position == Position.RIGHT) {
- lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
- } else {
- lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
- }
- lp.gravity = position.ANDROID_GRAVITY;
- return lp;
- }
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index b3c1f07..84578f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -88,6 +88,7 @@ import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarNotification;
+import com.android.internal.util.pie.PiePosition;
import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
@@ -106,7 +107,6 @@ import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NotificationRowLayout;
import com.android.systemui.statusbar.policy.OnSizeChangedListener;
import com.android.systemui.statusbar.policy.Prefs;
-import com.android.systemui.statusbar.policy.PieController.Position;
import com.android.systemui.statusbar.powerwidget.PowerWidget;
public class PhoneStatusBar extends BaseStatusBar {
@@ -485,7 +485,7 @@ public class PhoneStatusBar extends BaseStatusBar {
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Slog.v(TAG, "hasNavigationBar=" + showNav);
- if (showNav && !mRecreating) {
+ if (mNavigationBarView == null && showNav && !mRecreating) {
mNavigationBarView =
(NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);
@@ -1598,7 +1598,7 @@ public class PhoneStatusBar extends BaseStatusBar {
}
// don't allow expanding via e.g. service call while status bar is hidden
// due to expanded desktop
- if (mExpandedDesktopState == 2) {
+ if (getExpandedDesktopMode() == 2) {
return;
}
@@ -1670,7 +1670,7 @@ public class PhoneStatusBar extends BaseStatusBar {
}
// don't allow expanding via e.g. service call while status bar is hidden
// due to expanded desktop
- if (mExpandedDesktopState == 2) {
+ if (getExpandedDesktopMode() == 2) {
return;
}
@@ -2238,13 +2238,13 @@ public class PhoneStatusBar extends BaseStatusBar {
// hide pie triggers when keyguard is visible
try {
if (mWindowManagerService.isKeyguardLocked()) {
- updatePieTriggerMask(Position.BOTTOM.FLAG
- | Position.TOP.FLAG);
+ updatePieTriggerMask(PiePosition.BOTTOM.FLAG
+ | PiePosition.TOP.FLAG);
} else {
- updatePieTriggerMask(Position.LEFT.FLAG
- | Position.BOTTOM.FLAG
- | Position.RIGHT.FLAG
- | Position.TOP.FLAG);
+ updatePieTriggerMask(PiePosition.LEFT.FLAG
+ | PiePosition.BOTTOM.FLAG
+ | PiePosition.RIGHT.FLAG
+ | PiePosition.TOP.FLAG);
}
} catch (RemoteException e) {
// nothing else to do ...
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 1e94e97..f5fff53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -26,8 +26,9 @@ import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.util.pie.PiePosition;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.PieController.Position;
public class PhoneStatusBarView extends PanelBar {
private static final String TAG = "PhoneStatusBarView";
@@ -167,10 +168,10 @@ public class PhoneStatusBarView extends PanelBar {
mLastFullyOpenedPanel = null;
// show up you pie controls
- mBar.updatePieTriggerMask(Position.LEFT.FLAG
- | Position.TOP.FLAG
- | Position.RIGHT.FLAG
- | Position.TOP.FLAG);
+ mBar.updatePieTriggerMask(PiePosition.LEFT.FLAG
+ | PiePosition.TOP.FLAG
+ | PiePosition.RIGHT.FLAG
+ | PiePosition.TOP.FLAG);
}
@Override
@@ -182,9 +183,9 @@ public class PhoneStatusBarView extends PanelBar {
// back off you pie controls!
if (mShouldFade) {
- mBar.updatePieTriggerMask(Position.LEFT.FLAG
- | Position.RIGHT.FLAG
- | Position.TOP.FLAG);
+ mBar.updatePieTriggerMask(PiePosition.LEFT.FLAG
+ | PiePosition.RIGHT.FLAG
+ | PiePosition.TOP.FLAG);
}
mFadingPanel = openPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieItem.java b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieItem.java
index 6b64008..ce41278 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieItem.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieItem.java
@@ -28,9 +28,9 @@ import android.graphics.PorterDuff.Mode;
import android.view.View;
import android.widget.ImageView;
+import com.android.internal.util.pie.PiePosition;
import com.android.systemui.R;
-import com.android.systemui.statusbar.pie.PieLayout.PieDrawable;
-import com.android.systemui.statusbar.policy.PieController.Position;
+import com.android.systemui.statusbar.pie.PieView.PieDrawable;
/**
* A clickable pie menu item.
@@ -38,9 +38,9 @@ import com.android.systemui.statusbar.policy.PieController.Position;
* This is the actual end point for user interaction.<br>
* ( == This is what a user clicks on.)
*/
-public class PieItem extends PieLayout.PieDrawable {
+public class PieItem extends PieView.PieDrawable {
- private PieLayout mPieLayout;
+ private PieView mPieLayout;
private Paint mBackgroundPaint = new Paint();
private Paint mSelectedPaint = new Paint();
@@ -87,7 +87,7 @@ public class PieItem extends PieLayout.PieDrawable {
*/
public final static int CAN_LONG_PRESS = 0x400;
- public PieItem(Context context, PieLayout parent, int flags, int width, Object tag, View view) {
+ public PieItem(Context context, PieView parent, int flags, int width, Object tag, View view) {
mView = view;
mPieLayout = parent;
this.tag = tag;
@@ -129,9 +129,9 @@ public class PieItem extends PieLayout.PieDrawable {
public void show(boolean show) {
if (show) {
- flags |= PieLayout.PieDrawable.VISIBLE;
+ flags |= PieView.PieDrawable.VISIBLE;
} else {
- flags &= ~PieLayout.PieDrawable.VISIBLE;
+ flags &= ~PieView.PieDrawable.VISIBLE;
}
}
@@ -168,7 +168,7 @@ public class PieItem extends PieLayout.PieDrawable {
}
@Override
- public void prepare(Position position, float scale) {
+ public void prepare(PiePosition position, float scale) {
mPath = getOutline(scale);
if (mView != null) {
mView.measure(mView.getLayoutParams().width, mView.getLayoutParams().height);
@@ -188,7 +188,7 @@ public class PieItem extends PieLayout.PieDrawable {
}
@Override
- public void draw(Canvas canvas, Position position) {
+ public void draw(Canvas canvas, PiePosition position) {
if ((flags & SELECTED) != 0) {
Paint paint = (flags & LONG_PRESSED) == 0
? mSelectedPaint : mLongPressPaint;
@@ -202,7 +202,7 @@ public class PieItem extends PieLayout.PieDrawable {
int state = canvas.save();
canvas.translate(mView.getLeft(), mView.getTop());
// keep icons "upright" if we get displayed on TOP position
- if (position != Position.TOP) {
+ if (position != PiePosition.TOP) {
canvas.rotate(mStart + mSweep / 2 - 270);
} else {
canvas.rotate(mStart + mSweep / 2 - 90);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSliceContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSliceContainer.java
index c3a277a..90882e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSliceContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSliceContainer.java
@@ -18,8 +18,8 @@ package com.android.systemui.statusbar.pie;
import android.graphics.Canvas;
import android.util.Slog;
-import com.android.systemui.statusbar.pie.PieLayout.PieDrawable;
-import com.android.systemui.statusbar.policy.PieController.Position;
+import com.android.internal.util.pie.PiePosition;
+import com.android.systemui.statusbar.pie.PieView.PieDrawable;
import java.util.ArrayList;
import java.util.List;
@@ -27,19 +27,19 @@ import java.util.List;
/**
* A generic container for {@link PieItems}.
*/
-public class PieSliceContainer extends PieLayout.PieSlice {
+public class PieSliceContainer extends PieView.PieSlice {
- protected PieLayout mPieLayout;
+ protected PieView mPieLayout;
private List<PieItem> mItems = new ArrayList<PieItem>();
- public PieSliceContainer(PieLayout parent, int initialFlags) {
+ public PieSliceContainer(PieView parent, int initialFlags) {
mPieLayout = parent;
- flags = initialFlags | PieLayout.PieDrawable.VISIBLE;
+ flags = initialFlags | PieView.PieDrawable.VISIBLE;
}
@Override
- public void prepare(Position position, float scale) {
+ public void prepare(PiePosition position, float scale) {
if (hasItems()) {
int totalWidth = 0;
for (PieItem item : mItems) {
@@ -55,11 +55,11 @@ public class PieSliceContainer extends PieLayout.PieSlice {
float gapMinder = ((totalWidth * GAP * 2.0f) / (mOuter + mInner));
float deltaSweep = mSweep / totalWidth;
- int width = position != Position.TOP ? 0 : totalWidth;
+ int width = position != PiePosition.TOP ? 0 : totalWidth;
int viewMask = PieDrawable.VISIBLE | position.FLAG;
- boolean top = position == Position.TOP;
+ boolean top = position == PiePosition.TOP;
for (PieItem item : mItems) {
if ((item.flags & viewMask) == viewMask) {
if (top) width -= item.width;
@@ -68,8 +68,8 @@ public class PieSliceContainer extends PieLayout.PieSlice {
item.width * deltaSweep, mInner, mOuter);
item.setGap(deltaSweep * gapMinder);
- if (PieLayout.DEBUG) {
- Slog.d(PieLayout.TAG, "Layout " + item.tag + " : ("
+ if (PieView.DEBUG) {
+ Slog.d(PieView.TAG, "Layout " + item.tag + " : ("
+ (mStart + deltaSweep * width) + ","
+ (item.width * deltaSweep) + ")");
}
@@ -81,7 +81,7 @@ public class PieSliceContainer extends PieLayout.PieSlice {
}
@Override
- public void draw(Canvas canvas, Position gravity) {
+ public void draw(Canvas canvas, PiePosition gravity) {
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSysInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSysInfo.java
index 547a0a6..d5085da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSysInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieSysInfo.java
@@ -30,10 +30,9 @@ import android.net.wifi.WifiSsid;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import com.android.internal.util.pie.PiePosition;
import com.android.systemui.R;
-import com.android.systemui.statusbar.pie.PieLayout.PieDrawable;
import com.android.systemui.statusbar.policy.PieController;
-import com.android.systemui.statusbar.policy.PieController.Position;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -66,7 +65,7 @@ public class PieSysInfo extends PieSliceContainer implements ValueAnimator.Anima
private String mTimeFormatString;
private SimpleDateFormat mTimeFormat;
- public PieSysInfo(Context context, PieLayout parent,
+ public PieSysInfo(Context context, PieView parent,
PieController controller, int initialFlags) {
super(parent, initialFlags);
mController = controller;
@@ -84,7 +83,7 @@ public class PieSysInfo extends PieSliceContainer implements ValueAnimator.Anima
}
@Override
- public void prepare(Position position, float scale) {
+ public void prepare(PiePosition position, float scale) {
// We are updating data later when we starting to get visible.
// This does not save work on the main thread, but for fast gestures
@@ -97,8 +96,6 @@ public class PieSysInfo extends PieSliceContainer implements ValueAnimator.Anima
mInfoPaint.setAlpha(0);
final Resources res = mContext.getResources();
- final RectF innerBB = new RectF(-mInner * scale, -mInner * scale,
- mInner * scale, mInner * scale);
int textsize = res.getDimensionPixelSize(R.dimen.pie_textsize);
mInfoPaint.setTextSize(textsize * scale);
@@ -120,7 +117,7 @@ public class PieSysInfo extends PieSliceContainer implements ValueAnimator.Anima
}
@Override
- public void draw(Canvas canvas, Position position) {
+ public void draw(Canvas canvas, PiePosition position) {
// as long as there is no new data, we don't need to draw anything.
if (mStaleData) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieView.java
index c828dea..c850722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pie/PieView.java
@@ -31,9 +31,8 @@ import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.widget.FrameLayout;
-import com.android.systemui.statusbar.policy.PieController.Position;
+import com.android.internal.util.pie.PiePosition;
import com.android.systemui.R;
import java.util.ArrayList;
@@ -46,7 +45,7 @@ import java.util.List;
* processing the input events from the user.<br>
* (It handles the events for the snap points, too.)
*/
-public class PieLayout extends FrameLayout implements View.OnTouchListener {
+public class PieView extends View implements View.OnTouchListener {
public static final String TAG = "PieLayout";
public static final boolean DEBUG = false;
public static final boolean DEBUG_INPUT = false;
@@ -55,9 +54,6 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
private long mActivateStartDebug = 0;
private static final int TIME_FADEIN = 300;
- private static final int TIME_FADEIN_DELAY = 400;
-
- private static final int COLOR_BACKGROUND = 0xee000000;
private Paint mBackgroundPaint = new Paint();
private float mBackgroundFraction;
@@ -76,8 +72,8 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
private boolean mActive = false;
private int mPointerId;
private Point mCenter = new Point(0, 0);
- private Position mPosition = Position.BOTTOM;
- private Position mLayoutDoneForPosition;
+ private PiePosition mPosition = PiePosition.BOTTOM;
+ private PiePosition mLayoutDoneForPosition;
private Handler mHandler;
private Runnable mLongPressRunnable = new Runnable() {
@@ -109,7 +105,7 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
}
// animation updates occur on the main thread. it is save to call invalidate here.
- PieLayout.this.invalidate();
+ PieView.this.invalidate();
}
};
@@ -119,8 +115,8 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
* <p>
* This defines the basic geometry of a pie thing and provides the
* interface to trigger positioning and draw preparations
- * ({@link #prepare(Position, float)}), drawing
- * ({@link #draw(Canvas, Position)}) as well as user interaction
+ * ({@link #prepare(PiePosition, float)}), drawing
+ * ({@link #draw(Canvas, PiePosition)}) as well as user interaction
* ({@link #interact(float, int)}).
*/
public abstract static class PieDrawable {
@@ -129,9 +125,9 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
protected int mInner;
protected int mOuter;
- abstract public void prepare(Position position, float scale);
+ abstract public void prepare(PiePosition position, float scale);
- abstract public void draw(Canvas canvas, Position position);
+ abstract public void draw(Canvas canvas, PiePosition position);
abstract public PieItem interact(float alpha, int radius);
@@ -143,14 +139,14 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
}
// Display on all positions
- public final static int DISPLAY_ALL = Position.LEFT.FLAG
- | Position.BOTTOM.FLAG
- | Position.RIGHT.FLAG
- | Position.TOP.FLAG;
+ public final static int DISPLAY_ALL = PiePosition.LEFT.FLAG
+ | PiePosition.BOTTOM.FLAG
+ | PiePosition.RIGHT.FLAG
+ | PiePosition.TOP.FLAG;
// Display on all except the TOP position
- public final static int DISPLAY_NOT_AT_TOP = Position.LEFT.FLAG
- | Position.BOTTOM.FLAG
- | Position.RIGHT.FLAG;
+ public final static int DISPLAY_NOT_AT_TOP = PiePosition.LEFT.FLAG
+ | PiePosition.BOTTOM.FLAG
+ | PiePosition.RIGHT.FLAG;
// The PieDrawable is visible, note that slice visibility overrides item visibility
public final static int VISIBLE = 0x10;
@@ -168,7 +164,7 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
public final static float GAP = 3.0f;
/**
- * The slice will be considerer as important - {@link PieLayout} will try to keep
+ * The slice will be considerer as important - {@link PieView} will try to keep
* these slices on screen, when placing the pie control.
* @see PieDrawable#flags
*/
@@ -191,7 +187,7 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
private int mY;
private float mActivity;
- public SnapPoint(int x, int y, Position gravity) {
+ public SnapPoint(int x, int y, PiePosition gravity) {
mX = x;
mY = y;
mActivity = 0.0f;
@@ -219,7 +215,7 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
public boolean interact(float x, float y) {
float distanceSqr = (x - mX) * (x - mX) + (y - mY) * (y - mY);
if (distanceSqr - mSnapRadiusSqr < mSnapThresholdSqr) {
- PieLayout.this.invalidate();
+ PieView.this.invalidate();
if (distanceSqr < mSnapRadiusSqr) {
if (DEBUG) {
@@ -236,29 +232,34 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
return false;
}
- public final Position position;
+ public final PiePosition position;
}
private int mSnapPointMask = 0;
- private SnapPoint[] mSnapPoints = new SnapPoint[Position.values().length];
+ private SnapPoint[] mSnapPoints = new SnapPoint[PiePosition.values().length];
private SnapPoint mActiveSnap = null;
/**
* Listener interface for snap events on {@link SnapPoint}s.
*/
public interface OnSnapListener {
- void onSnap(Position position);
+ void onSnap(PiePosition position);
}
private OnSnapListener mOnSnapListener = null;
- public PieLayout(Context context) {
+ public interface OnExitListener {
+ void onExit();
+ }
+ private OnExitListener mOnExitListener = null;
+
+ public PieView(Context context) {
super(context);
mHandler = new Handler();
mBackgroundAnimator.addUpdateListener(mUpdateListener);
setDrawingCacheEnabled(false);
- setVisibility(View.GONE);
+ setVisibility(View.VISIBLE);
setWillNotDraw(false);
setFocusable(true);
setOnTouchListener(this);
@@ -271,6 +272,10 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
mOnSnapListener = onSnapListener;
}
+ public void setOnExitListener(OnExitListener onExitListener) {
+ mOnExitListener = onExitListener;
+ }
+
/**
* Tells the Layout where to show snap points.
* @param mask is a mask that corresponds to {@link Position}{@code .FLAG}.
@@ -310,11 +315,11 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
private void setupSnapPoints(int width, int height) {
mActiveSnap = null;
// reuse already created snap points
- for (Position g : Position.values()) {
- if ((mSnapPointMask & g.FLAG) != 0) {
+ for (PiePosition g : PiePosition.values()) {
+ if ((mSnapPointMask & g.FLAG) == 0) {
int x = width / 2;
int y = height / 2;
- if (g == Position.LEFT || g == Position.RIGHT) {
+ if (g == PiePosition.LEFT || g == PiePosition.RIGHT) {
x = g.FACTOR * width;
} else {
y = g.FACTOR * height;
@@ -447,11 +452,11 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
mActiveItem.onClickCall(mLongPressed);
}
}
- PieLayout.this.exit();
+ PieView.this.exit();
}
if (action == MotionEvent.ACTION_CANCEL) {
- PieLayout.this.exit();
+ PieView.this.exit();
}
}
return true;
@@ -533,7 +538,7 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
}
estimatedWidth = estimatedWidth * mPieScale;
- if (mPosition == Position.LEFT || mPosition == Position.RIGHT) {
+ if (mPosition == PiePosition.LEFT || mPosition == PiePosition.RIGHT) {
mCenter.x = mPadding + (int) ((getWidth() - 2 * mPadding) * mPosition.FACTOR);
if (estimatedWidth * 1.3f > getHeight()) {
mCenter.y = getHeight() / 2;
@@ -565,7 +570,7 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
getDimensions();
}
- public void activate(Point center, Position position) {
+ public void activate(Point center, PiePosition position) {
if (Looper.myLooper() != Looper.getMainLooper()) {
Slog.w(TAG, "Activation not on main thread: " + Thread.currentThread().getName());
}
@@ -573,7 +578,6 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
mActivateStartDebug = SystemClock.uptimeMillis();
getDimensions();
-
mPosition = position;
mLayoutDoneForPosition = null;
mActive = true;
@@ -593,15 +597,18 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
mBackgroundAnimator.setStartDelay(ViewConfiguration.getLongPressTimeout() * 2);
mBackgroundAnimator.start();
- setVisibility(View.VISIBLE);
-
-
Slog.d(TAG, "activate finished within "
+ (SystemClock.uptimeMillis() - mActivateStartDebug) + " ms");
}
public void exit() {
- setVisibility(View.GONE);
+ if (DEBUG) {
+ Slog.d(TAG, "Exiting pie now");
+ }
+ // if exit was called before, just ignore this one.
+ if (!mActive) {
+ return;
+ }
mBackgroundAnimator.cancel();
mActiveSnap = null;
@@ -614,6 +621,9 @@ public class PieLayout extends FrameLayout implements View.OnTouchListener {
updateActiveItem(null, false);
mActive = false;
+ if (mOnExitListener != null) {
+ mOnExitListener.onExit();
+ }
}
public void clearSlices() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PieController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PieController.java
index 7bc4588..d26ef8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PieController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PieController.java
@@ -16,10 +16,7 @@
*/
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
-import android.app.IActivityManager;
import android.app.SearchManager;
import android.app.StatusBarManager;
import android.content.ActivityNotFoundException;
@@ -28,15 +25,17 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.hardware.input.InputManager;
import android.os.BatteryManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -44,6 +43,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
+import android.service.pie.PieManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
@@ -53,36 +53,35 @@ import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.ViewGroup.LayoutParams;
-import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.Toast;
import com.android.internal.util.cm.DevUtils;
+import com.android.internal.util.pie.PiePosition;
import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.NavigationButtons;
import com.android.systemui.statusbar.NavigationButtons.ButtonInfo;
import com.android.systemui.statusbar.pie.PieItem;
-import com.android.systemui.statusbar.pie.PieLayout;
-import com.android.systemui.statusbar.pie.PieLayout.PieDrawable;
-import com.android.systemui.statusbar.pie.PieLayout.PieSlice;
+import com.android.systemui.statusbar.pie.PieView;
+import com.android.systemui.statusbar.pie.PieView.PieDrawable;
+import com.android.systemui.statusbar.pie.PieView.PieSlice;
import com.android.systemui.statusbar.pie.PieSliceContainer;
import com.android.systemui.statusbar.pie.PieSysInfo;
-import java.util.List;
-
/**
* Controller class for the default pie control.
* <p>
* This class is responsible for setting up the pie control, activating it, and defining and
* executing the actions that can be triggered by the pie control.
*/
-public class PieController implements BaseStatusBar.NavigationBarCallback,
- PieLayout.OnSnapListener, PieItem.PieOnClickListener, PieItem.PieOnLongClickListener {
+public class PieController implements BaseStatusBar.NavigationBarCallback, PieView.OnExitListener,
+ PieView.OnSnapListener, PieItem.PieOnClickListener, PieItem.PieOnLongClickListener {
public static final String TAG = "PieController";
public static final boolean DEBUG = false;
@@ -94,14 +93,18 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
private static final int MSG_INJECT_KEY_DOWN = 1066;
private static final int MSG_INJECT_KEY_UP = 1067;
+ private static final int MSG_PIE_GAIN_FOCUS = 1068;
+ private static final int MSG_PIE_RESTORE_LISTENER_STATE = 1069;
private Context mContext;
- private PieLayout mPieContainer;
+ private PieManager mPieManager;
+ private PieView mPieContainer;
/**
- * This is only needed for #toggleRecentApps()
+ * This is only needed for #toggleRecentApps() and #showSearchPanel()
*/
private BaseStatusBar mStatusBar;
private Vibrator mVibrator;
+ private WindowManager mWindowManager;
private IWindowManager mWm;
private int mBatteryLevel;
private int mBatteryStatus;
@@ -120,122 +123,36 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
private Drawable mBackIcon;
private Drawable mBackAltIcon;
- /**
- * Defines the positions in which pie controls may appear. This enumeration is used to store
- * an index, a flag and the android gravity for each position.
- */
- public enum Position {
- LEFT(0, 0, android.view.Gravity.LEFT),
- BOTTOM(1, 1, android.view.Gravity.BOTTOM),
- RIGHT(2, 1, android.view.Gravity.RIGHT),
- TOP(3, 0, android.view.Gravity.TOP);
-
- Position(int index, int factor, int android_gravity) {
- INDEX = index;
- FLAG = (0x01<<index);
- ANDROID_GRAVITY = android_gravity;
- FACTOR = factor;
- }
-
- public final int INDEX;
- public final int FLAG;
- public final int ANDROID_GRAVITY;
- /**
- * This is 1 when the position is not at the axis (like {@link Position.RIGHT} is
- * at {@code Layout.getWidth()} not at {@code 0}).
- */
- public final int FACTOR;
- }
-
- private Position mPosition;
-
- public static class Tracker {
- public static float sDistance;
- private float initialX = 0;
- private float initialY = 0;
- private float gracePeriod = 0;
-
- private Tracker(Position position) {
- this.position = position;
- }
-
- public void start(MotionEvent event) {
- initialX = event.getX();
- initialY = event.getY();
- switch (position) {
- case LEFT:
- gracePeriod = initialX + sDistance / 3.0f;
- break;
- case RIGHT:
- gracePeriod = initialX - sDistance / 3.0f;
- break;
- }
- active = true;
- }
+ protected int mExpandedDesktopState;
+ private int mPieTriggerSlots;
+ private int mPieTriggerMask = PiePosition.LEFT.FLAG
+ | PiePosition.BOTTOM.FLAG
+ | PiePosition.RIGHT.FLAG
+ | PiePosition.TOP.FLAG;
+ private PiePosition mPosition;
- public boolean move(MotionEvent event) {
- final float x = event.getX();
- final float y = event.getY();
- if (!active) {
- return false;
- }
-
- // Unroll the complete logic here - we want to be fast and out of the
- // event chain as fast as possible.
- boolean loaded = false;
- switch (position) {
- case LEFT:
- if (x < gracePeriod) {
- initialY = y;
- }
- if (initialY - y < sDistance && y - initialY < sDistance) {
- if (x - initialX <= sDistance) {
- return false;
- }
- loaded = true;
- }
- break;
- case BOTTOM:
- if (initialX - x < sDistance && x - initialX < sDistance) {
- if (initialY - y <= sDistance) {
- return false;
- }
- loaded = true;
- }
- break;
- case TOP:
- if (initialX - x < sDistance && x - initialX < sDistance) {
- if (y - initialY <= sDistance) {
- return false;
- }
- loaded = true;
- }
- break;
- case RIGHT:
- if (x > gracePeriod) {
- initialY = y;
- }
- if (initialY - y < sDistance && y - initialY < sDistance) {
- if (initialX - x <= sDistance) {
- return false;
- }
- loaded = true;
- }
- break;
+ private PieManager.PieActivationListener mPieActivationListener =
+ new PieManager.PieActivationListener(Looper.getMainLooper()) {
+ @Override
+ public void onPieActivation(int touchX, int touchY, PiePosition position, int flags) {
+ if (position == PiePosition.BOTTOM && isSearchLightEnabled() && mStatusBar != null) {
+ // if we are at the bottom and nothing else is there, use a
+ // search light!
+ mStatusBar.showSearchPanel();
+ // restore listener state immediately (after the bookkeeping), and since the
+ // search panel is a single gesture we will not trigger again
+ mHandler.obtainMessage(MSG_PIE_RESTORE_LISTENER_STATE).sendToTarget();
+ } else if (mPieContainer != null) {
+ // set the snap points depending on current trigger and mask
+ mPieContainer.setSnapPoints(mPieTriggerMask & ~mPieTriggerSlots);
+ activateFromListener(touchX, touchY, position);
+ // give the main thread some time to do the bookkeeping
+ mHandler.obtainMessage(MSG_PIE_GAIN_FOCUS).sendToTarget();
}
- active = false;
- return loaded;
}
+ };
- public boolean active = false;
- public final Position position;
- }
-
- public Tracker buildTracker(Position position) {
- return new Tracker(position);
- }
-
- private class H extends Handler {
+ private Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message m) {
final InputManager inputManager = InputManager.getInstance();
switch (m.what) {
@@ -248,10 +165,17 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
inputManager.injectInputEvent((KeyEvent) m.obj,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
break;
+ case MSG_PIE_GAIN_FOCUS:
+ if (!mPieActivationListener.gainTouchFocus(mPieContainer.getWindowToken())) {
+ mPieContainer.exit();
+ }
+ break;
+ case MSG_PIE_RESTORE_LISTENER_STATE:
+ mPieActivationListener.restoreListenerState();
+ break;
}
}
- }
- private H mHandler = new H();
+ };
private void injectKeyDelayed(int keyCode, long when) {
mHandler.removeMessages(MSG_INJECT_KEY_DOWN);
@@ -277,15 +201,41 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
void observe() {
ContentResolver resolver = mContext.getContentResolver();
+ // trigger setupNavigationItems()
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.NAV_BUTTONS), false, this);
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.KILL_APP_LONGPRESS_BACK), false, this);
+ // trigger setupContainer()
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.PIE_CONTROLS), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.EXPANDED_DESKTOP_STATE), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.EXPANDED_DESKTOP_STYLE), false, this);
+ // trigger setupListener()
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.PIE_POSITIONS), false, this);
}
@Override
public void onChange(boolean selfChange) {
- setupNavigationItems();
+ ContentResolver resolver = mContext.getContentResolver();
+ boolean expanded = Settings.System.getInt(resolver,
+ Settings.System.EXPANDED_DESKTOP_STATE, 0) == 1;
+ if (expanded) {
+ mExpandedDesktopState = Settings.System.getInt(resolver,
+ Settings.System.EXPANDED_DESKTOP_STYLE, 0);
+ } else {
+ mExpandedDesktopState = 0;
+ }
+ if (isEnabled()) {
+ setupContainer();
+ setupNavigationItems();
+ setupListener();
+ } else {
+ detachContainer();
+ }
}
}
@@ -301,7 +251,7 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
BatteryManager.BATTERY_STATUS_UNKNOWN);
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
// Give up on screen off. what's the point in pie controls if you don't see them?
- if (mPieContainer != null) {
+ if (isShowing()) {
mPieContainer.exit();
}
}
@@ -318,7 +268,9 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
public PieController(Context context) {
mContext = context;
+ mPieManager = PieManager.getInstance();
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
@@ -327,23 +279,29 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
}
final Resources res = mContext.getResources();
- Tracker.sDistance = res.getDimensionPixelSize(R.dimen.pie_trigger_distance);
mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);
mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
+
+ mPieManager.setPieActivationListener(mPieActivationListener);
+
+ // start listening for changes (calls setupListener & setupNavigationItems)
+ mSettingsObserver.observe();
+ mSettingsObserver.onChange(true);
}
- public void detachContainer() {
+ private void detachContainer() {
if (mPieContainer == null) {
return;
}
+ mPieManager.updatePieActivationListener(mPieActivationListener, 0);
+
if (mTelephonyManager != null) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
}
mContext.unregisterReceiver(mBroadcastReceiver);
- mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
mPieContainer.clearSlices();
mPieContainer = null;
@@ -353,15 +311,23 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
mStatusBar = statusBar;
}
- public void attachContainer(PieLayout container) {
- mPieContainer = container;
- mPieContainer.clearSlices();
+ private void setupContainer() {
+ if (mPieContainer == null) {
+ mPieContainer = new PieView(mContext);
+ mPieContainer.setOnSnapListener(this);
+ mPieContainer.setOnExitListener(this);
- if (DEBUG) {
- Slog.d(TAG, "Attaching to container: " + container);
+ if (mTelephonyManager != null) {
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ }
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
}
- mPieContainer.setOnSnapListener(this);
+ mPieContainer.clearSlices();
final Resources res = mContext.getResources();
@@ -371,7 +337,6 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
mNavigationSlice = new PieSliceContainer(mPieContainer, PieSlice.IMPORTANT
| PieDrawable.DISPLAY_ALL);
mNavigationSlice.setGeometry(START_ANGLE, 180 - 2 * EMPTY_ANGLE, inner, outer);
- setupNavigationItems();
mPieContainer.addSlice(mNavigationSlice);
// construct sysinfo slice
@@ -380,18 +345,15 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
mSysInfo = new PieSysInfo(mContext, mPieContainer, this, PieDrawable.DISPLAY_NOT_AT_TOP);
mSysInfo.setGeometry(START_ANGLE, 180 - 2 * EMPTY_ANGLE, inner, outer);
mPieContainer.addSlice(mSysInfo);
+ }
- // start listening for changes
- mSettingsObserver.observe();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ private void setupListener() {
+ ContentResolver resolver = mContext.getContentResolver();
- if (mTelephonyManager != null) {
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
- }
+ mPieTriggerSlots = Settings.System.getInt(resolver,
+ Settings.System.PIE_POSITIONS, PiePosition.BOTTOM.FLAG);
+ mPieManager.updatePieActivationListener(mPieActivationListener,
+ mPieTriggerSlots & mPieTriggerMask);
}
private void setupNavigationItems() {
@@ -462,14 +424,47 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
}
}
- public void activateFromTrigger(View view, MotionEvent event, Position position) {
- if (mPieContainer != null && !isShowing()) {
+ public void activateFromListener(int touchX, int touchY, PiePosition position) {
+ if (!isShowing()) {
doHapticTriggerFeedback();
mPosition = position;
- Point center = new Point((int) event.getRawX(), (int) event.getRawY());
+ Point center = new Point(touchX, touchY);
mPieContainer.activate(center, position);
- mPieContainer.invalidate();
+ mWindowManager.addView(mPieContainer, generateLayoutParam());
+ }
+ }
+
+ private WindowManager.LayoutParams generateLayoutParam() {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
+ PixelFormat.TRANSLUCENT);
+ // This title is for debugging only. See: dumpsys window
+ lp.setTitle("PieControlPanel");
+ lp.windowAnimations = android.R.style.Animation;
+ lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+ return lp;
+ }
+
+ @Override
+ public void onExit() {
+ mWindowManager.removeView(mPieContainer);
+ mPieActivationListener.restoreListenerState();
+ }
+
+ public void updatePieTriggerMask(int newMask) {
+ int oldState = mPieTriggerSlots & mPieTriggerMask;
+ mPieTriggerMask = newMask;
+
+ // first we check, if it would make a change
+ if ((mPieTriggerSlots & mPieTriggerMask) != oldState) {
+ setupListener();
}
}
@@ -557,7 +552,7 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
}
@Override
- public void onSnap(Position position) {
+ public void onSnap(PiePosition position) {
if (position == mPosition) {
return;
}
@@ -569,7 +564,7 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
}
int triggerSlots = Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.PIE_POSITIONS, Position.BOTTOM.FLAG);
+ Settings.System.PIE_POSITIONS, PiePosition.BOTTOM.FLAG);
triggerSlots = triggerSlots & ~mPosition.FLAG | position.FLAG;
@@ -660,13 +655,20 @@ public class PieController implements BaseStatusBar.NavigationBarCallback,
}
public boolean isShowing() {
- return mPieContainer != null && mPieContainer.isShowing();
+ return mPieContainer.isShowing();
}
public boolean isSearchLightEnabled() {
return mSearchLight != null && (mSearchLight.flags & PieDrawable.VISIBLE) != 0;
}
+ public boolean isEnabled() {
+ int pie = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.PIE_CONTROLS, 0);
+
+ return (pie == 1 && mExpandedDesktopState != 0) || pie == 2;
+ }
+
public String getOperatorState() {
if (mTelephonyManager == null) {
return null;
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);