From 74de02dee00dd73cfd3ef3af5bf549163cb7c233 Mon Sep 17 00:00:00 2001 From: masiullah Date: Sat, 14 Jul 2012 19:05:57 +0530 Subject: [PATCH] Stylus gestures features (1/2) Forward-port of https://github.com/CyanogenMod/android_frameworks_base/commit/9f3ee9f7c0877fcfbe90a7c176481f949194fb75 to JB Change-Id: I9c8a03dce226d2463659846ff3a12435efdf5c1d Conflicts: core/res/res/values/config.xml core/res/res/values/strings.xml core/res/res/values/symbols.xml --- core/java/android/provider/Settings.java | 49 ++++ core/res/res/values/config.xml | 3 + core/res/res/values/strings.xml | 4 +- core/res/res/values/symbols.xml | 5 + .../internal/policy/impl/PhoneWindow.java | 264 +++++++++++++++++- 5 files changed, 323 insertions(+), 2 deletions(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 50fbde9fb30..ad60dcd36ae 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3121,6 +3121,55 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String POWER_MENU_PROFILES_ENABLED = "power_menu_profiles_enabled"; + /** + * Enable Stylus Gestures + * + * @hide + */ + public static final String ENABLE_STYLUS_GESTURES = "enable_stylus_gestures"; + + /** + * Left Swipe Action + * + * @hide + */ + public static final String GESTURES_LEFT_SWIPE = "gestures_left_swipe"; + + /** + * Right Swipe Action + * + * @hide + */ + public static final String GESTURES_RIGHT_SWIPE = "gestures_right_swipe"; + + /** + * Up Swipe Action + * + * @hide + */ + public static final String GESTURES_UP_SWIPE = "gestures_up_swipe"; + + /** + * down Swipe Action + * + * @hide + */ + public static final String GESTURES_DOWN_SWIPE = "gestures_down_swipe"; + + /** + * Long press Action + * + * @hide + */ + public static final String GESTURES_LONG_PRESS = "gestures_long_press"; + + /** + * double tap Action + * + * @hide + */ + public static final String GESTURES_DOUBLE_TAP = "gestures_double_tap"; + /** * Whether power menu airplane toggle is enabled * @hide diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c35f657f33e..f0811a9e470 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1123,4 +1123,7 @@ true + + false + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 36aca89278e..42f06914af5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4161,5 +4161,7 @@ \u2264\u00ab\u2039 \u2265\u00bb\u203a - + + %s is not installed + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e98f706b0bb..cc07090fbd4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2005,4 +2005,9 @@ + + + + + diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 41d67bc6084..bde50e08cd0 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -40,11 +40,20 @@ import com.android.internal.widget.ActionBarView; import android.app.KeyguardManager; +import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.ActivityNotFoundException; +import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -53,10 +62,14 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; +import android.provider.Settings; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; @@ -65,6 +78,8 @@ import android.util.TypedValue; import android.view.ActionMode; import android.view.ContextThemeWrapper; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; import android.view.Gravity; import android.view.IRotationWatcher; import android.view.IWindowManager; @@ -92,10 +107,14 @@ import android.widget.PopupWindow; import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Toast; import java.lang.ref.WeakReference; import java.util.ArrayList; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.R; + /** * Android-specific Window. *

@@ -180,7 +199,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private int mTitleColor = 0; private boolean mAlwaysReadCloseOnTouchAttr = false; - + + private boolean mEnableGestures; + + private Context mContext; private ContextMenuBuilder mContextMenu; private MenuDialogHelper mContextMenuHelper; private boolean mClosingActionMenu; @@ -190,6 +212,36 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private AudioManager mAudioManager; private KeyguardManager mKeyguardManager; + private final class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System + .getUriFor(Settings.System.ENABLE_STYLUS_GESTURES), false, + this); + checkGestures(); + } + + void unobserve() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + checkGestures(); + } + + void checkGestures() { + mEnableGestures = Settings.System.getInt( + mContext.getContentResolver(), + Settings.System.ENABLE_STYLUS_GESTURES, 0) == 1; + } + } + private int mUiOptions = 0; private boolean mInvalidatePanelMenuPosted; @@ -215,6 +267,7 @@ static class WindowManagerHolder { public PhoneWindow(Context context) { super(context); + mContext = context; mLayoutInflater = LayoutInflater.from(context); } @@ -1812,9 +1865,12 @@ private final class DecorView extends FrameLayout implements RootViewSurfaceTake private PopupWindow mActionModePopup; private Runnable mShowActionModePopup; + private SettingsObserver mSettingsObserver; + public DecorView(Context context, int featureId) { super(context); mFeatureId = featureId; + mSettingsObserver = new SettingsObserver(new Handler()); } @Override @@ -1895,8 +1951,210 @@ public boolean dispatchKeyShortcutEvent(KeyEvent ev) { return false; } + private final StylusGestureFilter mStylusFilter = new StylusGestureFilter(); + + private class StylusGestureFilter extends SimpleOnGestureListener { + + private final static int SWIPE_UP = 1; + private final static int SWIPE_DOWN = 2; + private final static int SWIPE_LEFT = 3; + private final static int SWIPE_RIGHT = 4; + private final static int PRESS_LONG = 5; + private final static int TAP_DOUBLE = 6; + private final static double SWIPE_MIN_DISTANCE = 25.0; + private final static double SWIPE_MIN_VELOCITY = 50.0; + private final static int KEY_NO_ACTION = 1000; + private final static int KEY_HOME = 1001; + private final static int KEY_BACK = 1002; + private final static int KEY_MENU = 1003; + private final static int KEY_SEARCH = 1004; + private final static int KEY_RECENT = 1005; + private final static int KEY_APP = 1006; + private GestureDetector mDetector; + private final static String TAG = "StylusGestureFilter"; + + public StylusGestureFilter() { + mDetector = new GestureDetector(this); + } + + public boolean onTouchEvent(MotionEvent event) { + return mDetector.onTouchEvent(event); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, + float velocityX, float velocityY) { + + final float xDistance = Math.abs(e1.getX() - e2.getX()); + final float yDistance = Math.abs(e1.getY() - e2.getY()); + + velocityX = Math.abs(velocityX); + velocityY = Math.abs(velocityY); + boolean result = false; + + if (velocityX > (SWIPE_MIN_VELOCITY * getResources().getDisplayMetrics().density) + && xDistance > (SWIPE_MIN_DISTANCE * getResources().getDisplayMetrics().density) + && xDistance > yDistance) { + if (e1.getX() > e2.getX()) { // right to left + // Swipe Left + dispatchStylusAction(SWIPE_LEFT); + } else { + // Swipe Right + dispatchStylusAction(SWIPE_RIGHT); + } + result = true; + } else if (velocityY > (SWIPE_MIN_VELOCITY * getResources().getDisplayMetrics().density) + && yDistance > (SWIPE_MIN_DISTANCE * getResources().getDisplayMetrics().density) + && yDistance > xDistance) { + if (e1.getY() > e2.getY()) { // bottom to up + // Swipe Up + dispatchStylusAction(SWIPE_UP); + } else { + // Swipe Down + dispatchStylusAction(SWIPE_DOWN); + } + result = true; + } + return result; + } + + @Override + public boolean onDoubleTap(MotionEvent arg0) { + dispatchStylusAction(TAP_DOUBLE); + return true; + } + + public void onLongPress(MotionEvent e) { + dispatchStylusAction(PRESS_LONG); + } + + } + + private void menuAction() { + dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MENU)); + dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_MENU)); + + } + + private void backAction() { + dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_BACK)); + dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_BACK)); + } + + private void dispatchStylusAction(int gestureAction) { + final ContentResolver resolver = mContext.getContentResolver(); + boolean isSystemUI = mContext.getPackageName().equals("com.android.systemui"); + String setting = null; + int dispatchAction = -1; + switch (gestureAction) { + case StylusGestureFilter.SWIPE_LEFT: + setting = Settings.System.getString(resolver, + Settings.System.GESTURES_LEFT_SWIPE); + break; + case StylusGestureFilter.SWIPE_RIGHT: + setting = Settings.System.getString(resolver, + Settings.System.GESTURES_RIGHT_SWIPE); + break; + case StylusGestureFilter.SWIPE_UP: + setting = Settings.System.getString(resolver, + Settings.System.GESTURES_UP_SWIPE); + break; + case StylusGestureFilter.SWIPE_DOWN: + setting = Settings.System.getString(resolver, + Settings.System.GESTURES_DOWN_SWIPE); + break; + case StylusGestureFilter.TAP_DOUBLE: + setting = Settings.System.getString(resolver, + Settings.System.GESTURES_DOUBLE_TAP); + break; + case StylusGestureFilter.PRESS_LONG: + setting = Settings.System.getString(resolver, + Settings.System.GESTURES_LONG_PRESS); + break; + default: + return; + } + + try { + int value = Integer.valueOf(setting); + if (value == StylusGestureFilter.KEY_NO_ACTION) { + return; + } + dispatchAction = value; + } catch (NumberFormatException e) { + dispatchAction = StylusGestureFilter.KEY_APP; + } + + // Dispatching action + switch (dispatchAction) { + case StylusGestureFilter.KEY_HOME: + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(homeIntent); + break; + case StylusGestureFilter.KEY_BACK: + backAction(); + break; + case StylusGestureFilter.KEY_MENU: + // Menu action on notificationbar / systemui will be converted + // to back action + if (isSystemUI) { + backAction(); + break; + } + menuAction(); + break; + case StylusGestureFilter.KEY_SEARCH: + // Search action on notificationbar / systemui will be converted + // to back action + if (isSystemUI) { + backAction(); + break; + } + launchDefaultSearch(); + break; + case StylusGestureFilter.KEY_RECENT: + IStatusBarService mStatusBarService = IStatusBarService.Stub + .asInterface(ServiceManager.getService("statusbar")); + try { + mStatusBarService.toggleRecentApps(); + } catch (RemoteException e) { + } + break; + case StylusGestureFilter.KEY_APP: + // Launching app on notificationbar / systemui will be preceded + // with a back Action + if (isSystemUI) { + backAction(); + } + try { + final PackageManager pm = mContext.getPackageManager(); + Intent launchIntent = pm.getLaunchIntentForPackage(setting); + if (launchIntent != null) { + mContext.startActivity(launchIntent); + } + } catch (ActivityNotFoundException e) { + Toast.makeText(mContext, mContext.getString(R.string.stylus_app_not_installed, setting), + Toast.LENGTH_LONG).show(); + } + break; + } + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { + // Stylus events with side button pressed are filtered and other + // events are processed normally. + if (mEnableGestures + && MotionEvent.BUTTON_SECONDARY == ev.getButtonState()) { + mStylusFilter.onTouchEvent(ev); + return false; + } final Callback cb = getCallback(); return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); @@ -2462,6 +2720,8 @@ void updateWindowResizeState() { protected void onAttachedToWindow() { super.onAttachedToWindow(); + mSettingsObserver.observe(); + updateWindowResizeState(); final Callback cb = getCallback(); @@ -2485,6 +2745,8 @@ protected void onAttachedToWindow() { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + mSettingsObserver.unobserve(); + final Callback cb = getCallback(); if (cb != null && mFeatureId < 0) { cb.onDetachedFromWindow();