1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.apis.accessibility; 18 19 import android.accessibilityservice.AccessibilityService; 20 import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener; 21 import android.accessibilityservice.AccessibilityServiceInfo; 22 import android.graphics.Region; 23 import android.util.DisplayMetrics; 24 import android.util.Log; 25 import android.view.KeyEvent; 26 import android.view.accessibility.AccessibilityEvent; 27 28 /** 29 * This class is an {@link AccessibilityService} that controls the state of 30 * display magnification in response to key events. It demonstrates the 31 * following key features of the Android accessibility APIs: 32 * <ol> 33 * <li>Basic implementation of an AccessibilityService 34 * <li>Observing and respond to user-generated key events 35 * <li>Querying and modifying the state of display magnification 36 * </ol> 37 */ 38 public class MagnificationService extends AccessibilityService { 39 private static final String LOG_TAG = "MagnificationService"; 40 41 /** 42 * Callback for {@link android.view.accessibility.AccessibilityEvent}s. 43 * 44 * @param event An event. 45 */ 46 @Override onAccessibilityEvent(AccessibilityEvent event)47 public void onAccessibilityEvent(AccessibilityEvent event) { 48 // No events required for this service. 49 } 50 51 /** 52 * Callback for interrupting the accessibility feedback. 53 */ 54 @Override onInterrupt()55 public void onInterrupt() { 56 // No interruptible actions taken by this service. 57 } 58 59 /** 60 * Callback that allows an accessibility service to observe the key events 61 * before they are passed to the rest of the system. This means that the events 62 * are first delivered here before they are passed to the device policy, the 63 * input method, or applications. 64 * <p> 65 * <strong>Note:</strong> It is important that key events are handled in such 66 * a way that the event stream that would be passed to the rest of the system 67 * is well-formed. For example, handling the down event but not the up event 68 * and vice versa would generate an inconsistent event stream. 69 * </p> 70 * <p> 71 * <strong>Note:</strong> The key events delivered in this method are copies 72 * and modifying them will have no effect on the events that will be passed 73 * to the system. This method is intended to perform purely filtering 74 * functionality. 75 * <p> 76 * 77 * @param event The event to be processed. 78 * @return If true then the event will be consumed and not delivered to 79 * applications, otherwise it will be delivered as usual. 80 */ 81 @Override onKeyEvent(KeyEvent event)82 protected boolean onKeyEvent(KeyEvent event) { 83 // Only consume volume key events. 84 final int keyCode = event.getKeyCode(); 85 if (keyCode != KeyEvent.KEYCODE_VOLUME_UP 86 && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN) { 87 return false; 88 } 89 90 // Handle the event when the user releases the volume key. To prevent 91 // the keys from actually adjusting the device volume, we'll ignore 92 // the result of handleVolumeKey() and always return true to consume 93 // the events. 94 final int action = event.getAction(); 95 if (action == KeyEvent.ACTION_UP) { 96 handleVolumeKey(keyCode == KeyEvent.KEYCODE_VOLUME_UP); 97 } 98 99 // Consume all volume key events. 100 return true; 101 } 102 103 /** 104 * Adjusts the magnification scale in response to volume key actions. 105 * 106 * @param isVolumeUp {@code true} if the volume up key was pressed or 107 * {@code false} if the volume down key was pressed 108 * @return {@code true} if the magnification scale changed as a result of 109 * the key 110 */ handleVolumeKey(boolean isVolumeUp)111 private boolean handleVolumeKey(boolean isVolumeUp) { 112 // Obtain the controller on-demand, which allows us to avoid 113 // dependencies on the accessibility service's lifecycle. 114 final MagnificationController controller = getMagnificationController(); 115 116 // Adjust the current scale based on which volume key was pressed, 117 // constraining the scale between 1x and 5x. 118 final float currScale = controller.getScale(); 119 final float increment = isVolumeUp ? 0.1f : -0.1f; 120 final float nextScale = Math.max(1f, Math.min(5f, currScale + increment)); 121 if (nextScale == currScale) { 122 return false; 123 } 124 125 // Set the pivot, then scale around it. 126 final DisplayMetrics metrics = getResources().getDisplayMetrics(); 127 controller.setScale(nextScale, true /* animate */); 128 controller.setCenter(metrics.widthPixels / 2f, metrics.heightPixels / 2f, true); 129 return true; 130 } 131 132 /** 133 * This method is a part of the {@link AccessibilityService} lifecycle and is 134 * called after the system has successfully bound to the service. If is 135 * convenient to use this method for setting the {@link AccessibilityServiceInfo}. 136 * 137 * @see AccessibilityServiceInfo 138 * @see #setServiceInfo(AccessibilityServiceInfo) 139 */ 140 @Override onServiceConnected()141 public void onServiceConnected() { 142 final AccessibilityServiceInfo info = getServiceInfo(); 143 if (info == null) { 144 // If we fail to obtain the service info, the service is not really 145 // connected and we should avoid setting anything up. 146 return; 147 } 148 149 // We declared our intent to request key filtering in the meta-data 150 // attached to our service in the manifest. Now, we can explicitly 151 // turn on key filtering when needed. 152 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS; 153 setServiceInfo(info); 154 155 // Set up a listener for changes in the state of magnification. 156 getMagnificationController().addListener(new OnMagnificationChangedListener() { 157 @Override 158 public void onMagnificationChanged(MagnificationController controller, 159 Region region, float scale, float centerX, float centerY) { 160 Log.e(LOG_TAG, "Magnification scale is now " + scale); 161 } 162 }); 163 } 164 } 165