Java tutorial
/* * Copyright (C) 2013 Google Inc. * * 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.google.android.marvin.talkback; import android.annotation.TargetApi; import android.content.Context; import android.media.AudioManager; import android.os.Build; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import com.google.android.marvin.talkback.TalkBackService.KeyEventListener; import com.googlecode.eyesfree.utils.AccessibilityEventListener; import com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils; import com.googlecode.eyesfree.utils.WeakReferenceHandler; /** * Locks the volume control stream during a touch interaction event. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class ProcessorVolumeStream implements AccessibilityEventListener, KeyEventListener { /** Minimum API version required for this class to function. */ public static final int MIN_API_LEVEL = Build.VERSION_CODES.JELLY_BEAN_MR2; /** Default flags for volume adjustment while touching the screen. */ private static final int DEFAULT_FLAGS_TOUCHING_SCREEN = (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); /** Default flags for volume adjustment while not touching the screen. */ private static final int DEFAULT_FLAGS_NOT_TOUCHING_SCREEN = (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE | AudioManager.FLAG_PLAY_SOUND); /** Stream to control when the user is touching the screen. */ private static final int STREAM_TOUCHING_SCREEN = SpeechController.DEFAULT_STREAM; /** Stream to control when the user is not touching the screen. */ private static final int STREAM_DEFAULT = AudioManager.USE_DEFAULT_STREAM_TYPE; /** Tag used for identification of the wake lock held by this class */ private static final String WL_TAG = ProcessorVolumeStream.class.getSimpleName(); /** The service context */ private final Context mContext; /** The audio manager, used to adjust speech volume. */ private final AudioManager mAudioManager; /** WakeLock used to keep the screen active during key events */ private final WakeLock mWakeLock; /** * The cursor controller, used for determining the focused node and * navigating. */ private final CursorController mCursorController; /** * Feedback controller for providing feedback on boundaries during volume * key navigation. */ private final MappedFeedbackController mFeedbackController; /** Handler used for processing long-press key events */ private final LongPressHandler mLongPressHandler; /** Used for tracking which keys were long pressed and are still down */ private final SparseBooleanArray mKeysLongPressedAndDown = new SparseBooleanArray(); /** Whether the user is touching the screen. */ private boolean mTouchingScreen = false; @SuppressWarnings("deprecation") public ProcessorVolumeStream(TalkBackService service) { mContext = service; mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); mCursorController = service.getCursorController(); mFeedbackController = service.getFeedbackController(); mLongPressHandler = new LongPressHandler(this); final PowerManager pm = (PowerManager) service.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, WL_TAG); } @Override public void onAccessibilityEvent(AccessibilityEvent event) { switch (event.getEventType()) { case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: mTouchingScreen = true; break; case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: mTouchingScreen = false; break; } } @Override public boolean onKeyEvent(KeyEvent event) { boolean handled = false; switch (event.getAction()) { case KeyEvent.ACTION_DOWN: handled = handleKeyDown(event.getKeyCode()); break; case KeyEvent.ACTION_UP: handled = handleKeyUp(event.getKeyCode()); break; } if (handled) { // Quickly acquire and release the wake lock so that // PowerManager.ON_AFTER_RELEASE takes effect. mWakeLock.acquire(); mWakeLock.release(); } return handled; } private boolean handleKeyDown(int keyCode) { if (isFromVolumeKey(keyCode)) { handleVolumeKeyDown(keyCode); return true; } return false; } private boolean handleKeyUp(int keyCode) { if (isFromVolumeKey(keyCode)) { handleVolumeKeyUp(keyCode); return true; } return false; } private void handleVolumeKeyDown(int keyCode) { // Don't perform actions on key down, just track long-presses mLongPressHandler.sendMessageDelayed( Message.obtain(mLongPressHandler, LongPressHandler.MSG_LONG_PRESSED, keyCode), ViewConfiguration.getLongPressTimeout()); } private void handleVolumeKeyUp(int keyCode) { if (mKeysLongPressedAndDown.get(keyCode, false)) { // In the event of a volume key being long pressed, we assume the // user may have performed some other action, like taking a // screenshot. In this case, we won't take any action when we // receive an ACTION_UP from that key. mKeysLongPressedAndDown.delete(keyCode); return; } // Cancel any long-press messages for this key mLongPressHandler.removeMessages(LongPressHandler.MSG_LONG_PRESSED, keyCode); if (attemptEditTextNavigation(keyCode)) { return; } adjustVolumeFromKeyEvent(keyCode); } private void handleVolumeKeyLongPressed(int keyCode) { mKeysLongPressedAndDown.put(keyCode, true); if (mKeysLongPressedAndDown.size() == 2) { handleBothVolumeKeysLongPressed(); } } private void handleBothVolumeKeysLongPressed() { // TODO(caseyburkhardt): This works. We should do something awesome with it. } private boolean attemptEditTextNavigation(int keyCode) { AccessibilityNodeInfoCompat currentNode = mCursorController.getCursor(); try { if ((currentNode == null) || !AccessibilityNodeInfoUtils.nodeMatchesClassByType(mContext, currentNode, android.widget.EditText.class)) { return false; } final CursorGranularity currentGranularity = mCursorController.getGranularityAt(currentNode); if (currentGranularity == CursorGranularity.DEFAULT) { mCursorController.setGranularity(CursorGranularity.CHARACTER, false /* fromUser */); } boolean result = false; if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { result = mCursorController.next(false /* shouldWrap */, false /* shouldScroll */); } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { result = mCursorController.previous(false /* shouldWarp */, false /* shouldScroll */); } if (!result) { mFeedbackController.playAuditory(R.id.sounds_complete); } // Consume the key event, even if navigation in the EditText failed return true; } finally { AccessibilityNodeInfoUtils.recycleNodes(currentNode); } } private void adjustVolumeFromKeyEvent(int keyCode) { final int direction = ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER); if (mTouchingScreen) { mAudioManager.adjustStreamVolume(STREAM_TOUCHING_SCREEN, direction, DEFAULT_FLAGS_TOUCHING_SCREEN); } else { // Attempt to adjust the suggested stream, but let the system // override in special situations like during voice calls, when an // application has locked the volume control stream, or when music // is playing. mAudioManager.adjustSuggestedStreamVolume(direction, STREAM_DEFAULT, DEFAULT_FLAGS_NOT_TOUCHING_SCREEN); } } private boolean isFromVolumeKey(int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: return true; } return false; } private static class LongPressHandler extends WeakReferenceHandler<ProcessorVolumeStream> { public static final int MSG_LONG_PRESSED = 1; public LongPressHandler(ProcessorVolumeStream parent) { super(parent); } @Override protected void handleMessage(Message msg, ProcessorVolumeStream parent) { if (MSG_LONG_PRESSED == msg.what) { parent.handleVolumeKeyLongPressed((Integer) msg.obj); } } } }