diff options
26 files changed, 1953 insertions, 570 deletions
@@ -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); |
