diff --git a/MotoActions/src/com/moto/actions/KeyHandler.java b/MotoActions/src/com/moto/actions/KeyHandler.java new file mode 100644 index 0000000..4c50d74 --- /dev/null +++ b/MotoActions/src/com/moto/actions/KeyHandler.java @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * Copyright (C) 2017 The LineageOS Project + * + * 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.moto.actions; + +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.ISearchManager; +import android.app.KeyguardManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.input.InputManager; +import android.media.AudioAttributes; +import android.media.session.MediaSessionLegacyHelper; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.MediaStore; +import android.provider.Settings; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +import com.android.internal.os.DeviceKeyHandler; +import com.android.internal.util.ArrayUtils; + +import com.moto.actions.util.FileUtils; + +import java.util.List; + +import static com.moto.actions.actions.Constants.*; + +public class KeyHandler implements DeviceKeyHandler { + + private static final String TAG = KeyHandler.class.getSimpleName(); + + private static final int GESTURE_REQUEST = 1; + private static final int FP_ACTION_REQUEST = 2; + + private static final String ACTION_DISMISS_KEYGUARD = + "com.android.keyguard.action.DISMISS_KEYGUARD_SECURELY"; + + private static final String GESTURE_WAKEUP_REASON = "keyhandler-gesture-wakeup"; + private static final int GESTURE_WAKELOCK_DURATION = 3000; + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .build(); + private final Context mContext; + private final PowerManager mPowerManager; + WakeLock mGestureWakeLock; + private KeyguardManager mKeyguardManager; + private FPScreenOffGesturesHandler mFPScreenOffGesturesHandler; + private CameraManager mCameraManager; + private String mRearCameraId; + private boolean mTorchEnabled; + private Vibrator mVibrator; + private ISearchManager mSearchManagerService; + private Handler mHandler; + private int fpTapCounts = 0; + private boolean fpTapPending = false; + private boolean fpGesturePending = false; + private Runnable doubleTapRunnable = new Runnable() { + public void run() { + int action = 0; + if (fpTapCounts > 1) { + action = str2int(FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEY_DBLTAP_NODE))); + } else { + if (isSingleTapEnabledOnFP()) { + action = str2int(FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEYS_NODE))); + } + } + + if (action != 0) { + boolean isActionSupported = ArrayUtils.contains(mPowerManager.isScreenOn() ? sFPSupportedActions : sFPSupportedActionsScreenOff, action); + if (isActionSupported) { + fireFPAction(action, true); + } + } + resetDoubleTapOnFP(); + } + }; + private Runnable fpGestureRunnable = new Runnable() { + public void run() { + resetFPGestureDelay(); + } + }; + + public KeyHandler(Context context) { + mContext = context; + + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mFPScreenOffGesturesHandler = new FPScreenOffGesturesHandler(); + + mGestureWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "GestureWakeLock"); + + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + if (mVibrator == null || !mVibrator.hasVibrator()) { + mVibrator = null; + } + + mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); + mCameraManager.registerTorchCallback(new MyTorchCallback(), null); + + mHandler = new Handler(Looper.getMainLooper()); + } + + static long[] getLongIntArray(Resources r, int resid) { + int[] ar = r.getIntArray(resid); + if (ar == null) { + return null; + } + long[] out = new long[ar.length]; + for (int i = 0; i < ar.length; i++) { + out[i] = ar[i]; + } + return out; + } + + private static ActivityInfo getRunningActivityInfo(Context context) { + final ActivityManager am = (ActivityManager) context + .getSystemService(Context.ACTIVITY_SERVICE); + final PackageManager pm = context.getPackageManager(); + + List tasks = am.getRunningTasks(1); + if (tasks != null && !tasks.isEmpty()) { + ActivityManager.RunningTaskInfo top = tasks.get(0); + try { + return pm.getActivityInfo(top.topActivity, 0); + } catch (PackageManager.NameNotFoundException e) { + } + } + return null; + } + + private static void dispatchMediaKeyWithWakeLock(int keycode, Context context) { + if (ActivityManagerNative.isSystemReady()) { + KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), KeyEvent.ACTION_DOWN, keycode, 0); + MediaSessionLegacyHelper.getHelper(context).sendMediaButtonEvent(event, true); + event = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); + MediaSessionLegacyHelper.getHelper(context).sendMediaButtonEvent(event, true); + } + } + + private static void switchToLastApp(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.RunningTaskInfo lastTask = getLastTask(context, am); + + if (lastTask != null) { + am.moveTaskToFront(lastTask.id, ActivityManager.MOVE_TASK_NO_USER_ACTION); + } + } + + private static ActivityManager.RunningTaskInfo getLastTask(Context context, + final ActivityManager am) { + final String defaultHomePackage = resolveCurrentLauncherPackage(context); + List tasks = am.getRunningTasks(5); + + for (int i = 1; i < tasks.size(); i++) { + String packageName = tasks.get(i).topActivity.getPackageName(); + if (!packageName.equals(defaultHomePackage) + && !packageName.equals(context.getPackageName()) + && !packageName.equals("com.android.systemui")) { + return tasks.get(i); + } + } + return null; + } + + private static String resolveCurrentLauncherPackage(Context context) { + final Intent launcherIntent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME); + final PackageManager pm = context.getPackageManager(); + final ResolveInfo launcherInfo = pm.resolveActivity(launcherIntent, 0); + return launcherInfo.activityInfo.packageName; + } + + private String getRearCameraId() { + if (mRearCameraId == null) { + try { + for (final String cameraId : mCameraManager.getCameraIdList()) { + CameraCharacteristics characteristics = + mCameraManager.getCameraCharacteristics(cameraId); + int cOrientation = characteristics.get(CameraCharacteristics.LENS_FACING); + if (cOrientation == CameraCharacteristics.LENS_FACING_BACK) { + mRearCameraId = cameraId; + break; + } + } + } catch (CameraAccessException e) { + // Ignore + } + } + return mRearCameraId; + } + + private Intent getLaunchableIntent(Intent intent) { + PackageManager pm = mContext.getPackageManager(); + List resInfo = pm.queryIntentActivities(intent, 0); + if (resInfo.isEmpty()) { + return null; + } + return pm.getLaunchIntentForPackage(resInfo.get(0).activityInfo.packageName); + } + + private void triggerCameraAction() { + ensureKeyguardManager(); + WakeLock wl = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "GestureWakeLock"); + wl.acquire(500); + if (mKeyguardManager.inKeyguardRestrictedInputMode()) { + launchSecureCamera(); + } else { + launchCamera(); + } + } + + private void launchCamera() { + Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_FROM_BACKGROUND); + if (getBestActivityInfo(intent) != null) { + // Only launch if we can succeed, but let the user pick the action + mContext.startActivity(intent); + } + } + + private void launchSecureCamera() { + // Keyguard won't allow a picker, try to pick the secure intent in the package + // that would be the one used for a default action of launching the camera + Intent normalIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); + normalIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + normalIntent.addFlags(Intent.FLAG_FROM_BACKGROUND); + + Intent secureIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE); + secureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + secureIntent.addFlags(Intent.FLAG_FROM_BACKGROUND); + + ActivityInfo normalActivity = getBestActivityInfo(normalIntent); + ActivityInfo secureActivity = getBestActivityInfo(secureIntent, normalActivity); + if (secureActivity != null) { + secureIntent.setComponent(new ComponentName(secureActivity.applicationInfo.packageName, secureActivity.name)); + mContext.startActivity(secureIntent); + } + } + + private ActivityInfo getBestActivityInfo(Intent intent) { + PackageManager pm = mContext.getPackageManager(); + ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); + if (resolveInfo != null) { + return resolveInfo.activityInfo; + } else { + // If the resolving failed, just find our own best match + return getBestActivityInfo(intent, null); + } + } + + private ActivityInfo getBestActivityInfo(Intent intent, ActivityInfo match) { + PackageManager pm = mContext.getPackageManager(); + List activities = pm.queryIntentActivities(intent, 0); + ActivityInfo best = null; + if (activities.size() > 0) { + best = activities.get(0).activityInfo; + if (match != null) { + String packageName = match.applicationInfo.packageName; + for (int i = activities.size() - 1; i >= 0; i--) { + ActivityInfo activityInfo = activities.get(i).activityInfo; + if (packageName.equals(activityInfo.applicationInfo.packageName)) { + best = activityInfo; + } + } + } + } + return best; + } + + private void openBrowser() { + mGestureWakeLock.acquire(GESTURE_WAKELOCK_DURATION); + mPowerManager.wakeUp(SystemClock.uptimeMillis(), GESTURE_WAKEUP_REASON); + final Intent intent = getLaunchableIntent( + new Intent(Intent.ACTION_VIEW, Uri.parse("http:"))); + startActivitySafely(intent); + } + + private void openDialer() { + mGestureWakeLock.acquire(GESTURE_WAKELOCK_DURATION); + mPowerManager.wakeUp(SystemClock.uptimeMillis(), GESTURE_WAKEUP_REASON); + final Intent intent = new Intent(Intent.ACTION_DIAL, null); + startActivitySafely(intent); + } + + private void openEmail() { + mGestureWakeLock.acquire(GESTURE_WAKELOCK_DURATION); + mPowerManager.wakeUp(SystemClock.uptimeMillis(), GESTURE_WAKEUP_REASON); + final Intent intent = getLaunchableIntent( + new Intent(Intent.ACTION_VIEW, Uri.parse("mailto:"))); + startActivitySafely(intent); + } + + private void openMessages() { + mGestureWakeLock.acquire(GESTURE_WAKELOCK_DURATION); + mPowerManager.wakeUp(SystemClock.uptimeMillis(), GESTURE_WAKEUP_REASON); + final String defaultApplication = Settings.Secure.getString( + mContext.getContentResolver(), "sms_default_application"); + final PackageManager pm = mContext.getPackageManager(); + final Intent intent = pm.getLaunchIntentForPackage(defaultApplication); + if (intent != null) { + startActivitySafely(intent); + } + } + + private void toggleFlashlight() { + String rearCameraId = getRearCameraId(); + if (rearCameraId != null) { + mGestureWakeLock.acquire(GESTURE_WAKELOCK_DURATION); + try { + mCameraManager.setTorchMode(rearCameraId, !mTorchEnabled); + mTorchEnabled = !mTorchEnabled; + } catch (CameraAccessException e) { + // Ignore + } + } + } + + private void ensureKeyguardManager() { + if (mKeyguardManager == null) { + mKeyguardManager = + (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + } + } + + private void resetDoubleTapOnFP() { + fpTapCounts = 0; + fpTapPending = false; + mHandler.removeCallbacks(doubleTapRunnable); + } + + private void detectDoubleTapOnFP() { + fpTapCounts++; + if (fpTapCounts == 1 || fpTapCounts == 2) { + doHapticFeedbackFP(false); + } + if (!fpTapPending) { + fpTapPending = true; + mHandler.postDelayed(doubleTapRunnable, ViewConfiguration.getDoubleTapTimeout()); + } + } + + private boolean isSingleTapEnabledOnFP() { + return !FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEYS_NODE)).equals("0"); + } + + private boolean isDoubleTapEnabledOnFP() { + return !FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEY_DBLTAP_NODE)).equals("0"); + } + + private boolean isHapticFeedbackEnabledOnFP() { + return !FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_HAPTIC_NODE)).equals("0"); + } + + private String getFPNodeBasedOnScreenState(String node) { + if (mPowerManager.isScreenOn()) { + return node; + } + switch (node) { + case FP_KEYS_NODE: + return FP_KEYS_SCREENOFF_NODE; + case FP_HAPTIC_NODE: + return FP_HAPTIC_SCREENOFF_NODE; + case FP_KEY_DBLTAP_NODE: + return FP_KEY_SCREENOFF_DBLTAP_NODE; + case FP_KEY_HOLD_NODE: + return FP_KEY_SCREENOFF_HOLD_NODE; + case FP_KEY_RIGHT_NODE: + return FP_KEY_SCREENOFF_RIGHT_NODE; + case FP_KEY_LEFT_NODE: + return FP_KEY_SCREENOFF_LEFT_NODE; + } + return node; + } + + public boolean handleKeyEvent(KeyEvent event) { + int scanCode = event.getScanCode(); + + if (DEBUG) { + Log.d(TAG, "DEBUG: action=" + event.getAction() + + ", flags=" + event.getFlags() + + ", keyCode=" + event.getKeyCode() + + ", scanCode=" + event.getScanCode() + + ", metaState=" + event.getMetaState() + + ", repeatCount=" + event.getRepeatCount()); + } + + boolean isFPScanCode = ArrayUtils.contains(sSupportedFPGestures, scanCode); + if (!isFPScanCode) { + return false; + } + + boolean isFPGestureEnabled = FileUtils.readOneLine(FP_HOME_NODE).equals("1"); + boolean isFPGestureEnabledOnScreenOff = FileUtils.readOneLine(FP_HOME_OFF_NODE).equals("1"); + + boolean isScreenOn = mPowerManager.isScreenOn(); + + // We only want ACTION_UP event + if (event.getAction() != KeyEvent.ACTION_UP) { + return true; + } + + if (isFPScanCode){ + if (fpGesturePending) { + return false; + } else { + resetFPGestureDelay(); + fpGesturePending = true; + mHandler.postDelayed(fpGestureRunnable, 10); + } + } + + if (scanCode != FP_TAP_SCANCODE) { + resetDoubleTapOnFP(); + } + + if (isFPScanCode) { + if ((!isFPGestureEnabled) || (!isScreenOn && !isFPGestureEnabledOnScreenOff)) { + resetDoubleTapOnFP(); + return false; + } + if (!isScreenOn && isFPGestureEnabledOnScreenOff) { + processFPScreenOffScancode(scanCode); + } else { + processFPScancode(scanCode); + } + } + return true; + } + + private void processFPScancode(int scanCode) { + int action = 0; + boolean isScreenOn = mPowerManager.isScreenOn(); + switch (scanCode) { + case FP_TAP_SCANCODE: + if (isDoubleTapEnabledOnFP()) { + detectDoubleTapOnFP(); + return; + } else { + resetDoubleTapOnFP(); + action = str2int(FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEYS_NODE))); + } + break; + case FP_HOLD_SCANCODE: + action = str2int(FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEY_HOLD_NODE))); + break; + case FP_RIGHT_SCANCODE: + action = str2int(FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEY_RIGHT_NODE))); + break; + case FP_LEFT_SCANCODE: + action = str2int(FileUtils.readOneLine(getFPNodeBasedOnScreenState(FP_KEY_LEFT_NODE))); + break; + } + boolean isActionSupported = ArrayUtils.contains(isScreenOn ? sFPSupportedActions : sFPSupportedActionsScreenOff, action); + if (isActionSupported) { + fireFPAction(action, false); + } + } + + private void fireFPAction(int action, boolean isDoubleTap) { + ensureKeyguardManager(); + boolean isHapticFeedbackEnabledOnFP = isHapticFeedbackEnabledOnFP(); + if (isDoubleTap && action != ACTION_CAMERA && action != ACTION_FLASHLIGHT) { + isHapticFeedbackEnabledOnFP = false; + } + if (isHapticFeedbackEnabledOnFP){ + if (action == ACTION_CAMERA || action == ACTION_FLASHLIGHT) { + vibrate(action == ACTION_CAMERA ? 500 : 250); + }else if (action != ACTION_VOICE_ASSISTANT) { + doHapticFeedbackFP(false); + } + } + switch (action) { + case ACTION_HOME: + if (!mKeyguardManager.inKeyguardRestrictedInputMode()) { + triggerVirtualKeypress(mHandler, KeyEvent.KEYCODE_HOME); + } + break; + case ACTION_POWER: + toggleScreenState(); + break; + case ACTION_BACK: + triggerVirtualKeypress(mHandler, KeyEvent.KEYCODE_BACK); + break; + case ACTION_RECENTS: + if (!mKeyguardManager.inKeyguardRestrictedInputMode()) { + triggerVirtualKeypress(mHandler, KeyEvent.KEYCODE_APP_SWITCH); + } + break; + case ACTION_VOLUME_UP: + triggerVirtualKeypress(mHandler, KeyEvent.KEYCODE_VOLUME_UP); + break; + case ACTION_VOLUME_DOWN: + triggerVirtualKeypress(mHandler, KeyEvent.KEYCODE_VOLUME_DOWN); + break; + case ACTION_VOICE_ASSISTANT: + if (!mKeyguardManager.inKeyguardRestrictedInputMode()) { + fireGoogleNowOnTap(); + } + return; + case ACTION_PLAY_PAUSE: + dispatchMediaKeyWithWakeLock(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, mContext); + break; + case ACTION_PREVIOUS_TRACK: + dispatchMediaKeyWithWakeLock(KeyEvent.KEYCODE_MEDIA_PREVIOUS, mContext); + break; + case ACTION_NEXT_TRACK: + dispatchMediaKeyWithWakeLock(KeyEvent.KEYCODE_MEDIA_NEXT, mContext); + break; + case ACTION_FLASHLIGHT: + toggleFlashlight(); + break; + case ACTION_CAMERA: + triggerCameraAction(); + break; + case ACTION_SCREENSHOT: + triggerVirtualKeypress(mHandler, KeyEvent.KEYCODE_SYSRQ); + break; + case ACTION_LAST_APP: + if (!mKeyguardManager.inKeyguardRestrictedInputMode()) { + switchToLastApp(mContext); + } + break; + } + } + + private void vibrate(int intensity) { + if (mVibrator == null) { + return; + } + mVibrator.vibrate(intensity); + } + + private void toggleScreenState() { + if (mPowerManager.isScreenOn()) { + mPowerManager.goToSleep(SystemClock.uptimeMillis()); + } else { + mPowerManager.wakeUp(SystemClock.uptimeMillis()); + } + } + + private void triggerVirtualKeypress(final Handler handler, final int keyCode) { + final InputManager im = InputManager.getInstance(); + long now = SystemClock.uptimeMillis(); + + final KeyEvent downEvent = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, + keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_CLASS_BUTTON); + final KeyEvent upEvent = KeyEvent.changeAction(downEvent, + KeyEvent.ACTION_UP); + + // add a small delay to make sure everything behind got focus + handler.postDelayed(new Runnable() { + @Override + public void run() { + im.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + }, 10); + + handler.postDelayed(new Runnable() { + @Override + public void run() { + im.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + }, 20); + } + + private void fireGoogleNowOnTap() { + doHapticFeedbackFP(true); + mSearchManagerService = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE)); + if (mSearchManagerService != null) { + try { + mSearchManagerService.launchAssist(new Bundle()); + } catch (RemoteException e) { + } + } + } + + private int str2int(String str) { + if (str == null || str.isEmpty()) { + return 0; + } + try { + return Integer.valueOf(str); + } catch (Exception e) { + return 0; + } + } + + private void processFPScreenOffScancode(int scanCode) { + if (!mFPScreenOffGesturesHandler.hasMessages(FP_ACTION_REQUEST)) { + Message msg = mFPScreenOffGesturesHandler.obtainMessage(FP_ACTION_REQUEST); + msg.arg1 = scanCode; + mFPScreenOffGesturesHandler.sendMessage(msg); + } + } + + private void resetFPGestureDelay() { + fpGesturePending = false; + mHandler.removeCallbacks(fpGestureRunnable); + } + + private void fireScreenOffAction(int action) { + boolean haptic = Settings.System.getInt(mContext.getContentResolver(), KEY_GESTURE_ENABLE_HAPTIC_FEEDBACK, 1) != 0; + if (haptic && (action == ACTION_CAMERA || action == ACTION_FLASHLIGHT)) { + vibrate(action == ACTION_CAMERA ? 500 : 250); + } + if (haptic && action == ACTION_POWER){ + doHapticFeedbackScreenOff(); + } + switch (action) { + case ACTION_POWER: + toggleScreenState(); + break; + case ACTION_PLAY_PAUSE: + dispatchMediaKeyWithWakeLock(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, mContext); + break; + case ACTION_PREVIOUS_TRACK: + dispatchMediaKeyWithWakeLock(KeyEvent.KEYCODE_MEDIA_PREVIOUS, mContext); + break; + case ACTION_NEXT_TRACK: + dispatchMediaKeyWithWakeLock(KeyEvent.KEYCODE_MEDIA_NEXT, mContext); + break; + case ACTION_FLASHLIGHT: + toggleFlashlight(); + break; + case ACTION_CAMERA: + triggerCameraAction(); + break; + case ACTION_BROWSER: + openBrowser(); + break; + case ACTION_DIALER: + openDialer(); + break; + case ACTION_EMAIL: + openEmail(); + break; + case ACTION_MESSAGES: + openMessages(); + break; + } + if (action != ACTION_FLASHLIGHT && action != ACTION_CAMERA && action != ACTION_POWER) { + doHapticFeedbackScreenOff(); + } + } + + private void startActivitySafely(Intent intent) { + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + try { + UserHandle user = new UserHandle(UserHandle.USER_CURRENT); + mContext.startActivityAsUser(intent, null, user); + } catch (ActivityNotFoundException e) { + // Ignore + } + } + + private void doHapticFeedbackScreenOff() { + if (mVibrator == null) { + return; + } + boolean enabled = Settings.System.getInt(mContext.getContentResolver(), KEY_GESTURE_ENABLE_HAPTIC_FEEDBACK, 1) != 0; + if (enabled) { + mVibrator.vibrate(50); + } + } + + private void doHapticFeedbackFP(boolean longpress) { + if (mVibrator == null) { + return; + } + + if (isHapticFeedbackEnabledOnFP()) { + mHandler.post(new Runnable() { + public void run() { + int owningUid; + String owningPackage; + owningUid = android.os.Process.myUid(); + owningPackage = mContext.getOpPackageName(); + VibrationEffect effect = VibrationEffect.createOneShot(longpress ? 50 : 40, VibrationEffect.DEFAULT_AMPLITUDE); + mVibrator.vibrate(owningUid, owningPackage, effect, VIBRATION_ATTRIBUTES); + } + }); + } + } + + private class FPScreenOffGesturesHandler extends Handler { + @Override + public void handleMessage(Message msg) { + processFPScancode(msg.arg1); + } + } + + private class MyTorchCallback extends CameraManager.TorchCallback { + @Override + public void onTorchModeChanged(String cameraId, boolean enabled) { + if (!cameraId.equals(mRearCameraId)) + return; + mTorchEnabled = enabled; + } + + @Override + public void onTorchModeUnavailable(String cameraId) { + if (!cameraId.equals(mRearCameraId)) + return; + mTorchEnabled = false; + } + } +} diff --git a/overlay/frameworks/base/core/res/res/values/config.xml b/overlay/frameworks/base/core/res/res/values/config.xml index 32484d0..01851bc 100644 --- a/overlay/frameworks/base/core/res/res/values/config.xml +++ b/overlay/frameworks/base/core/res/res/values/config.xml @@ -319,6 +319,12 @@ false + + /system/priv-app/MotoActions/MotoActions.apk + + + com.moto.actions.KeyHandler +