Home | History | Annotate | Download | only in accessibility
      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
     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
     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
     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      */
    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
    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