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