Network Android

/*
 * Copyright (C) 2010 The Android Open Source 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 app.test;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
import java.util.List;
/**
 * This class is an {@link AccessibilityService} that provides custom feedback
 * for the Clock application that comes by default with Android devices. It
 * demonstrates the following key features of the Android accessibility APIs:
 * 

     *   

  1.  *     Simple demonstration of how to use the accessibility APIs.
     *   

  2.  *   

  3.  *     Hands-on example of various ways to utilize the accessibility API for
     *     providing alternative and complementary feedback.
     *   

  4.  *   

  5.  *     Providing application specific feedback — the service handles only
     *     accessibility events from the clock application.
     *   

  6.  *   

  7.  *     Providing dynamic, context-dependent feedback — feedback type changes
     *     depending on the ringer state.
     *   

  8.  *   

  9.  *     Application specific UI enhancement - application domain knowledge is
     *     utilized to enhance the provided feedback.
     *   

  10.  * 

 * 


 *   
 *     Note: This code sample will work only on devices shipped with the default Clock
 *     application. If you are running Android 1.6 of Android 2.0 you should enable first
 *     ClockBack and then TalkBack since in these releases accessibility services are
 *     notified in the order of registration.
 *   

 * 


 */
public class Test extends AccessibilityService {
    /** Tag for logging from this service. */
    private static final String LOG_TAG = "ClockBackService";
    // Fields for configuring how the system handles this accessibility service.
    /** Minimal timeout between accessibility events we want to receive. */
    private static final int EVENT_NOTIFICATION_TIMEOUT_MILLIS = 80;
    /** Packages we are interested in.
     * 


     *   
     *   Note: This code sample will work only on devices shipped with the
     *   default Clock application.
     *   

     * 


     */
    // This works with AlarmClock and Clock whose package name changes in different releases
    private static final String[] PACKAGE_NAMES = new String[] {
            "com.android.alarmclock", "com.google.android.deskclock", "com.android.deskclock"
    };
    // Message types we are passing around.
    /** Speak. */
    private static final int MESSAGE_SPEAK = 1;
    /** Stop speaking. */
    private static final int MESSAGE_STOP_SPEAK = 2;
    /** Start the TTS service. */
    private static final int MESSAGE_START_TTS = 3;
    /** Stop the TTS service. */
    private static final int MESSAGE_SHUTDOWN_TTS = 4;
    /** Play an earcon. */
    private static final int MESSAGE_PLAY_EARCON = 5;
    /** Stop playing an earcon. */
    private static final int MESSAGE_STOP_PLAY_EARCON = 6;
    /** Vibrate a pattern. */
    private static final int MESSAGE_VIBRATE = 7;
    /** Stop vibrating. */
    private static final int MESSAGE_STOP_VIBRATE = 8;
    // Screen state broadcast related constants.
    /** Feedback mapping index used as a key for the screen-on broadcast. */
    private static final int INDEX_SCREEN_ON = 0x00000100;
    /** Feedback mapping index used as a key for the screen-off broadcast. */
    private static final int INDEX_SCREEN_OFF = 0x00000200;
    // Ringer mode change related constants.
    /** Feedback mapping index used as a key for normal ringer mode. */
    private static final int INDEX_RINGER_NORMAL = 0x00000400;
    /** Feedback mapping index used as a key for vibration ringer mode. */
    private static final int INDEX_RINGER_VIBRATE = 0x00000800;
    /** Feedback mapping index used as a key for silent ringer mode. */
    private static final int INDEX_RINGER_SILENT = 0x00001000;
    // Speech related constants.
    /**
     * The queuing mode we are using - interrupt a spoken utterance before
     * speaking another one.
     */
    private static final int QUEUING_MODE_INTERRUPT = 2;
    /** The space string constant. */
    private static final String SPACE = " ";
    /**
     * The class name of the number picker buttons with no text we want to
     * announce in the Clock application.
     */
    private static final String CLASS_NAME_NUMBER_PICKER_BUTTON_CLOCK = "android.widget.NumberPickerButton";
    /**
     * The class name of the number picker buttons with no text we want to
     * announce in the AlarmClock application.
     */
    private static final String CLASS_NAME_NUMBER_PICKER_BUTTON_ALARM_CLOCK = "com.android.internal.widget.NumberPickerButton";
    /**
     * The class name of the edit text box for hours and minutes we want to
     * better announce.
     */
    private static final String CLASS_NAME_EDIT_TEXT = "android.widget.EditText";
    /**
     * Mapping from integer to string resource id where the keys are generated
     * from the {@link AccessibilityEvent#getText()},
     * {@link AccessibilityEvent#getItemCount()} and
     * {@link AccessibilityEvent#getCurrentItemIndex()} properties.
     * 


     * Note: In general, computing these mappings includes the widget position on
     * the screen. This is fragile and should be used as a last resort since
     * changing the layout could potentially change the widget position. This is
     * a workaround since the widgets of interest are image buttons that do not
     * have contentDescription attribute set (plus/minus buttons) or no other
     * information in the accessibility event is available to distinguish them
     * aside of their positions on the screen (hour/minute inputs).
     * If you are owner of the target application (Clock in this case) you
     * should add contentDescription attribute to all image buttons such that a
     * screen reader knows how to speak them. For input fields (while not
     * applicable for the hour and minute inputs since they are not empty) a
     * hint text should be set to enable better announcement.
     * 


     */
    private static final SparseArray sEventDataMappedStringResourceIds = new SparseArray();
    static {
        sEventDataMappedStringResourceIds.put(110, R.string.value_increase_hours);
        sEventDataMappedStringResourceIds.put(1140, R.string.value_increase_minutes);
        sEventDataMappedStringResourceIds.put(1120, R.string.value_decrease_hours);
        sEventDataMappedStringResourceIds.put(1160, R.string.value_decrease_minutes);
        sEventDataMappedStringResourceIds.put(1111, R.string.value_hour);
        sEventDataMappedStringResourceIds.put(1110, R.string.value_hours);
        sEventDataMappedStringResourceIds.put(1151, R.string.value_minute);
        sEventDataMappedStringResourceIds.put(1150, R.string.value_minutes);
    }
    /** Mapping from integers to vibration patterns for haptic feedback. */
    private static final SparseArray sVibrationPatterns = new SparseArray();
    static {
        sVibrationPatterns.put(AccessibilityEvent.TYPE_VIEW_CLICKED, new long[] {
                0L, 100L
        });
        sVibrationPatterns.put(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED, new long[] {
                0L, 100L
        });
        sVibrationPatterns.put(AccessibilityEvent.TYPE_VIEW_SELECTED, new long[] {
                0L, 15L, 10L, 15L
        });
        sVibrationPatterns.put(AccessibilityEvent.TYPE_VIEW_FOCUSED, new long[] {
                0L, 15L, 10L, 15L
        });
        sVibrationPatterns.put(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, new long[] {
                0L, 25L, 50L, 25L, 50L, 25L
        });
        sVibrationPatterns.put(INDEX_SCREEN_ON, new long[] {
                0L, 10L, 10L, 20L, 20L, 30L
        });
        sVibrationPatterns.put(INDEX_SCREEN_OFF, new long[] {
                0L, 30L, 20L, 20L, 10L, 10L
        });
    }
    /** Mapping from integers to raw sound resource ids. */
    private static SparseArray sSoundsResourceIds = new SparseArray();
    static {
        sSoundsResourceIds.put(AccessibilityEvent.TYPE_VIEW_CLICKED, R.raw.sound_view_clicked);
        sSoundsResourceIds.put(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED, R.raw.sound_view_clicked);
        sSoundsResourceIds.put(AccessibilityEvent.TYPE_VIEW_SELECTED, R.raw.sound_view_focused_or_selected);
        sSoundsResourceIds.put(AccessibilityEvent.TYPE_VIEW_FOCUSED, R.raw.sound_view_focused_or_selected);
        sSoundsResourceIds.put(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, R.raw.sound_window_state_changed);
        sSoundsResourceIds.put(INDEX_SCREEN_ON, R.raw.sound_screen_on);
        sSoundsResourceIds.put(INDEX_SCREEN_OFF, R.raw.sound_screen_off);
        sSoundsResourceIds.put(INDEX_RINGER_SILENT, R.raw.sound_ringer_silent);
        sSoundsResourceIds.put(INDEX_RINGER_VIBRATE, R.raw.sound_ringer_vibrate);
        sSoundsResourceIds.put(INDEX_RINGER_NORMAL, R.raw.sound_ringer_normal);
    }
    // Sound pool related member fields.
    /** Mapping from integers to earcon names - dynamically populated. */
    private final SparseArray mEarconNames = new SparseArray();
    // Auxiliary fields.
    /**
     * Handle to this service to enable inner classes to access the {@link Context}.
     */
    Context mContext;
    /** The feedback this service is currently providing. */
    int mProvidedFeedbackType;
    /** Reusable instance for building utterances. */
    private final StringBuilder mUtterance = new StringBuilder();
    // Feedback providing services.
    /** The {@link TextToSpeech} used for speaking. */
    private TextToSpeech mTts;
    /** The {@link AudioManager} for detecting ringer state. */
    private AudioManager mAudioManager;
    /** Vibrator for providing haptic feedback. */
    private Vibrator mVibrator;
    /** Flag if the infrastructure is initialized. */
    private boolean isInfrastructureInitialized;
    /** {@link Handler} for executing messages on the service main thread. */
    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case MESSAGE_SPEAK:
                    String utterance = (String) message.obj;
                    mTts.speak(utterance, QUEUING_MODE_INTERRUPT, null);
                    return;
                case MESSAGE_STOP_SPEAK:
                    mTts.stop();
                    return;
                case MESSAGE_START_TTS:
                    mTts = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() {
                        public void onInit(int status) {
                            // Register here since to add earcons the TTS must be initialized and
                            // the receiver is called immediately with the current ringer mode.
                            registerBroadCastReceiver();
                        }
                    });
                    return;
                case MESSAGE_SHUTDOWN_TTS:
                    mTts.shutdown();
                    return;
                case MESSAGE_PLAY_EARCON:
                    int resourceId = message.arg1;
                    playEarcon(resourceId);
                    return;
                case MESSAGE_STOP_PLAY_EARCON:
                    mTts.stop();
                    return;
                case MESSAGE_VIBRATE:
                    int key = message.arg1;
                    long[] pattern = sVibrationPatterns.get(key);
                    mVibrator.vibrate(pattern, -1);
                    return;
                case MESSAGE_STOP_VIBRATE:
                    mVibrator.cancel();
                    return;
            }
        }
    };
    /**
     * {@link BroadcastReceiver} for receiving updates for our context - device
     * state.
     */
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
                int ringerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE,
                        AudioManager.RINGER_MODE_NORMAL);
                configureForRingerMode(ringerMode);
            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
                provideScreenStateChangeFeedback(INDEX_SCREEN_ON);
            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                provideScreenStateChangeFeedback(INDEX_SCREEN_OFF);
            } else {
                Log.w(LOG_TAG, "Registered for but not handling action " + action);
            }
        }
        /**
         * Provides feedback to announce the screen state change. Such a change
         * is turning the screen on or off.
         *
         * @param feedbackIndex The index of the feedback in the statically
         *            mapped feedback resources.
         */
        private void provideScreenStateChangeFeedback(int feedbackIndex) {
            // We take a specific action depending on the feedback we currently provide.
            switch (mProvidedFeedbackType) {
                case AccessibilityServiceInfo.FEEDBACK_SPOKEN:
                    String utterance = generateScreenOnOrOffUtternace(feedbackIndex);
                    mHandler.obtainMessage(MESSAGE_SPEAK, utterance).sendToTarget();
                    return;
                case AccessibilityServiceInfo.FEEDBACK_AUDIBLE:
                    mHandler.obtainMessage(MESSAGE_PLAY_EARCON, feedbackIndex, 0).sendToTarget();
                    return;
                case AccessibilityServiceInfo.FEEDBACK_HAPTIC:
                    mHandler.obtainMessage(MESSAGE_VIBRATE, feedbackIndex, 0).sendToTarget();
                    return;
                default:
                    throw new IllegalStateException("Unexpected feedback type "
                            + mProvidedFeedbackType);
            }
        }
    };
    @Override
    public void onServiceConnected() {
        if (isInfrastructureInitialized) {
            return;
        }
        mContext = this;
        // Send a message to start the TTS.
        mHandler.sendEmptyMessage(MESSAGE_START_TTS);
        // Get the vibrator service.
        mVibrator = (Vibrator) getSystemService(Service.VIBRATOR_SERVICE);
        // Get the AudioManager and configure according the current ring mode.
        mAudioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
        // In Froyo the broadcast receiver for the ringer mode is called back with the
        // current state upon registering but in Eclair this is not done so we poll here.
        int ringerMode = mAudioManager.getRingerMode();
        configureForRingerMode(ringerMode);
        // We are in an initialized state now.
        isInfrastructureInitialized = true;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        if (isInfrastructureInitialized) {
            // Stop the TTS service.
            mHandler.sendEmptyMessage(MESSAGE_SHUTDOWN_TTS);
            // Unregister the intent broadcast receiver.
            if (mBroadcastReceiver != null) {
                unregisterReceiver(mBroadcastReceiver);
            }
            // We are not in an initialized state anymore.
            isInfrastructureInitialized = false;
        }
        return false;
    }
    /**
     * Registers the phone state observing broadcast receiver.
     */
    private void registerBroadCastReceiver() {
        // Create a filter with the broadcast intents we are interested in.
        IntentFilter filter = new IntentFilter();
        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        // Register for broadcasts of interest.
        registerReceiver(mBroadcastReceiver, filter, null, null);
    }
    /**
     * Generates an utterance for announcing screen on and screen off.
     *
     * @param feedbackIndex The feedback index for looking up feedback value.
     * @return The utterance.
     */
    private String generateScreenOnOrOffUtternace(int feedbackIndex) {
        // Get the announce template.
        int resourceId = (feedbackIndex == INDEX_SCREEN_ON) ? R.string.template_screen_on
                : R.string.template_screen_off;
        String template = mContext.getString(resourceId);
        // Format the template with the ringer percentage.
        int currentRingerVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
        int maxRingerVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
        int volumePercent = (100 / maxRingerVolume) * currentRingerVolume;
        // Let us round to five so it sounds better.
        int adjustment = volumePercent % 10;
        if (adjustment < 5) {
            volumePercent -= adjustment;
        } else if (adjustment > 5) {
            volumePercent += (10 - adjustment);
        }
        return String.format(template, volumePercent);
    }
    /**
     * Configures the service according to a ringer mode. Possible
     * configurations:
     * 


     *   1. {@link AudioManager#RINGER_MODE_SILENT}
     *   Goal:     Provide only custom haptic feedback.
     *   Approach: Take over the haptic feedback by configuring this service to provide
     *             such and do so. This way the system will not call the default haptic
     *             feedback service KickBack.
     *             Take over the audible and spoken feedback by configuring this
     *             service to provide such feedback but not doing so. This way the system
     *             will not call the default spoken feedback service TalkBack and the
     *             default audible feedback service SoundBack.
     * 


     * 


     *   2. {@link AudioManager#RINGER_MODE_VIBRATE}
     *   Goal:     Provide custom audible and default haptic feedback.
     *   Approach: Take over the audible feedback and provide custom one.
     *             Take over the spoken feedback but do not provide such.
     *             Let some other service provide haptic feedback (KickBack).
     * 


     * 


     *   3. {@link AudioManager#RINGER_MODE_NORMAL}
     *   Goal:     Provide custom spoken, default audible and default haptic feedback.
     *   Approach: Take over the spoken feedback and provide custom one.
     *             Let some other services provide audible feedback (SounBack) and haptic
     *             feedback (KickBack).
     * 


     * Note: In the above description an assumption is made that all default feedback
     *       services are enabled. Such services are TalkBack, SoundBack, and KickBack.
     *       Also the feature of defining a service as the default for a given feedback
     *       type will be available in Android 2.2 and above. For previous releases the package
     *       specific accessibility service must be registered first i.e. checked in the
     *       settings.
     *
     * @param ringerMode The device ringer mode.
     */
    private void configureForRingerMode(int ringerMode) {
        if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
            // When the ringer is silent we want to provide only haptic feedback.
            mProvidedFeedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC;
            // Take over the spoken and sound feedback so no such feedback is provided.
            setServiceInfo(AccessibilityServiceInfo.FEEDBACK_HAPTIC
                    | AccessibilityServiceInfo.FEEDBACK_SPOKEN
                    | AccessibilityServiceInfo.FEEDBACK_AUDIBLE);
            // Use only an earcon to announce ringer state change.
            mHandler.obtainMessage(MESSAGE_PLAY_EARCON, INDEX_RINGER_SILENT, 0).sendToTarget();
        } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
            // When the ringer is vibrating we want to provide only audible feedback.
            mProvidedFeedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
            // Take over the spoken feedback so no spoken feedback is provided.
            setServiceInfo(AccessibilityServiceInfo.FEEDBACK_AUDIBLE
                    | AccessibilityServiceInfo.FEEDBACK_SPOKEN);
            // Use only an earcon to announce ringer state change.
            mHandler.obtainMessage(MESSAGE_PLAY_EARCON, INDEX_RINGER_VIBRATE, 0).sendToTarget();
        } else if (ringerMode == AudioManager.RINGER_MODE_NORMAL) {
            // When the ringer is ringing we want to provide spoken feedback
            // overriding the default spoken feedback.
            mProvidedFeedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
            setServiceInfo(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
            // Use only an earcon to announce ringer state change.
            mHandler.obtainMessage(MESSAGE_PLAY_EARCON, INDEX_RINGER_NORMAL, 0).sendToTarget();
        }
    }
    /**
     * Sets the {@link AccessibilityServiceInfo} which informs the system how to
     * handle this {@link AccessibilityService}.
     *
     * @param feedbackType The type of feedback this service will provide.
     * 


     *   Note: The feedbackType parameter is an bitwise or of all
     *   feedback types this service would like to provide.
     * 


     */
    private void setServiceInfo(int feedbackType) {
        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
        // We are interested in all types of accessibility events.
        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
        // We want to provide specific type of feedback.
        info.feedbackType = feedbackType;
        // We want to receive events in a certain interval.
        info.notificationTimeout = EVENT_NOTIFICATION_TIMEOUT_MILLIS;
        // We want to receive accessibility events only from certain packages.
        info.packageNames = PACKAGE_NAMES;
        setServiceInfo(info);
    }
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.i(LOG_TAG, mProvidedFeedbackType + " " + event.toString());
        // Here we act according to the feedback type we are currently providing.
        if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
            mHandler.obtainMessage(MESSAGE_SPEAK, formatUtterance(event)).sendToTarget();
        } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_AUDIBLE) {
            mHandler.obtainMessage(MESSAGE_PLAY_EARCON, event.getEventType(), 0).sendToTarget();
        } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_HAPTIC) {
            mHandler.obtainMessage(MESSAGE_VIBRATE, event.getEventType(), 0).sendToTarget();
        } else {
            throw new IllegalStateException("Unexpected feedback type " + mProvidedFeedbackType);
        }
    }
    @Override
    public void onInterrupt() {
        // Here we act according to the feedback type we are currently providing.
        if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
            mHandler.obtainMessage(MESSAGE_STOP_SPEAK).sendToTarget();
        } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_AUDIBLE) {
            mHandler.obtainMessage(MESSAGE_STOP_PLAY_EARCON).sendToTarget();
        } else if (mProvidedFeedbackType == AccessibilityServiceInfo.FEEDBACK_HAPTIC) {
            mHandler.obtainMessage(MESSAGE_STOP_VIBRATE).sendToTarget();
        } else {
            throw new IllegalStateException("Unexpected feedback type " + mProvidedFeedbackType);
        }
    }
    /**
     * Formats an utterance from an {@link AccessibilityEvent}.
     *
     * @param event The event from which to format an utterance.
     * @return The formatted utterance.
     */
    private String formatUtterance(AccessibilityEvent event) {
        StringBuilder utterance = mUtterance;
        // Clear the utterance before appending the formatted text.
        utterance.setLength(0);
        List eventText = event.getText();
        // We try to get the event text if such.
        if (!eventText.isEmpty()) {
            for (CharSequence subText : eventText) {
                // Make 01 pronounced as 1
                if (subText.charAt(0) =='0') {
                    subText = subText.subSequence(1, subText.length());
                }
                utterance.append(subText);
                utterance.append(SPACE);
            }
            // Here we do a bit of enhancement of the UI presentation by using the semantic
            // of the event source in the context of the Clock application.
            if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
                    && CLASS_NAME_EDIT_TEXT.equals(event.getClassName())) {
                // If the source is an edit text box and we have a mapping based on
                // its position in the items of the container parent of the event source
                // we append that value as well. We say "XX hours" and "XX minutes".
                String resourceValue = getEventDataMappedStringResource(event);
                if (resourceValue != null) {
                    utterance.append(resourceValue);
                }
            }
            return utterance.toString();
        }
        // There is no event text but we try to get the content description which is
        // an optional attribute for describing a view (typically used with ImageView).
        CharSequence contentDescription = event.getContentDescription();
        if (contentDescription != null) {
            utterance.append(contentDescription);
            return utterance.toString();
        }
        // No text and content description for the plus and minus buttons, so we lookup
        // custom values based on the event's itemCount and currentItemIndex properties.
        CharSequence className = event.getClassName();
        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
                && (CLASS_NAME_NUMBER_PICKER_BUTTON_ALARM_CLOCK.equals(className)
                || CLASS_NAME_NUMBER_PICKER_BUTTON_CLOCK.equals(className))) {
            String resourceValue = getEventDataMappedStringResource(event);
            utterance.append(resourceValue);
        }
        return utterance.toString();
    }
    /**
     * Returns a string resource mapped based on the accessibility event
     * data, specifically the
     * {@link AccessibilityEvent#getText()},
     * {@link AccessibilityEvent#getItemCount()}, and
     * {@link AccessibilityEvent#getCurrentItemIndex()} properties.
     *
     * @param event The {@link AccessibilityEvent} to process.
     * @return The mapped string if such exists, null otherwise.
     */
    private String getEventDataMappedStringResource(AccessibilityEvent event) {
        int lookupIndex = computeLookupIndex(event);
        int resourceId = sEventDataMappedStringResourceIds.get(lookupIndex);
        return getString(resourceId);
    }
    /**
     * Computes an index for looking up the custom text for views which either
     * do not have text/content description or the position information
     * is the only oracle for deciding from which widget was an accessibility
     * event generated. The index is computed based on
     * {@link AccessibilityEvent#getText()},
     * {@link AccessibilityEvent#getItemCount()}, and
     * {@link AccessibilityEvent#getCurrentItemIndex()} properties.
     *
     * @param event The event from which to compute the index.
     * @return The lookup index.
     */
    private int computeLookupIndex(AccessibilityEvent event) {
        int lookupIndex = event.getItemCount();
        int divided = event.getCurrentItemIndex();
        while (divided > 0) {
            lookupIndex *= 10;
            divided /= 10;
        }
        lookupIndex += event.getCurrentItemIndex();
        lookupIndex *= 10;
        // This is primarily for handling the zero hour/zero minutes cases
        if (!event.getText().isEmpty()
                && ("1".equals(event.getText().get(0).toString()) || "01".equals(event.getText()
                        .get(0).toString()))) {
            lookupIndex++;
        }
        return lookupIndex;
    }
    /**
     * Plays an earcon given its id.
     *
     * @param earconId The id of the earcon to be played.
     */
    private void playEarcon(int earconId) {
        String earconName = mEarconNames.get(earconId);
        if (earconName == null) {
            // We do not know the sound id, hence we need to load the sound.
            int resourceId = sSoundsResourceIds.get(earconId);
            earconName = "[" + earconId + "]";
            mTts.addEarcon(earconName, getPackageName(), resourceId);
            mEarconNames.put(earconId, earconName);
        }
        mTts.playEarcon(earconName, QUEUING_MODE_INTERRUPT, null);
    }
}
//values/strings.xml


    
    
    ClockBack

    
    Increase hours

    
    Increase minutes
    
    Decrease hours
    
    Decrease minutes
    
    hour
    
    hours
    
    minute
    
    minutes
    
    Ringer audible
    
    Ringer vibrate
    
    Ringer silent
    
    
    Screen on. Volume %1$s percent.
    
    Screen off. Volume %1$s percent.