<commit_before>/*
 * Copyright 2012 <NAME>
 *
 * 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.pilot51.voicenotify;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.IllegalFormatException;
import java.util.Timer;
import java.util.TimerTask;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Notification;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.PowerManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;

public class Service extends AccessibilityService {
	private static String TAG = Service.class.getSimpleName();
	private String lastMsg = "";
	private long lastMsgTime;
	private TextToSpeech mTts;
	private AudioManager audioMan;
	private TelephonyManager telephony;
	private final DeviceStateReceiver stateReceiver = new DeviceStateReceiver();
	private RepeatTimer repeater;
	private Shake shake;
	private static boolean isInitialized, isSuspended, isScreenOn, isHeadsetPlugged, isBluetoothConnected;
	private final HashMap<String, String> ttsParams = new HashMap<String, String>();
	private final ArrayList<String> ignoreReasons = new ArrayList<String>(),
	                                repeatList = new ArrayList<String>();
	private String lastQueueTime;
	private OnStatusChangeListener statusListener = new OnStatusChangeListener() {
		@Override
		public void onStatusChanged() {
			if (isSuspended && mTts != null) mTts.stop();
			sendBroadcast(new Intent(getApplicationContext(), WidgetProvider.class)
			              .setAction(WidgetProvider.ACTION_UPDATE));
		}
	};
	
	@Override
	public void onAccessibilityEvent(AccessibilityEvent event) {
		if (!Common.getPrefs(this).getBoolean(getString(R.string.key_toasts), false) && !(event.getParcelableData() instanceof Notification)) {
			return;
		}
		long newMsgTime = System.currentTimeMillis();
		StringBuilder notifyMsg = new StringBuilder();
		if (!event.getText().isEmpty()) {
			for (CharSequence subText : event.getText()) {
				notifyMsg.append(subText);
			}
		}
		final String ttsStringPref = Common.getPrefs(this).getString(getString(R.string.key_ttsString), null);
		App app = AppList.findOrAddApp(event.getPackageName().toString(), this);
		NotifyList.addNotification(app, notifyMsg.toString());
		String newMsg;
		try {
			newMsg = String.format(ttsStringPref.replace("%t", "%1$s").replace("%m", "%2$s"), app.getLabel(), notifyMsg.toString().replaceAll("[\\|\\[\\]\\{\\}\\*<>]+", " "));
		} catch(IllegalFormatException e) {
			Log.w(TAG, "Error formatting custom TTS string!");
			e.printStackTrace();
			newMsg = ttsStringPref;
		}
		final String[] ignoreStrings = Common.getPrefs(this).getString(getString(R.string.key_ignore_strings), "").toLowerCase().split("\n");
		boolean stringIgnored = false;
		if (ignoreStrings != null) {
			for (String s : ignoreStrings) {
				if (s.length() != 0 && notifyMsg.toString().toLowerCase().contains(s)) {
					stringIgnored = true;
					break;
				}
			}
		}
		if (isSuspended) {
			ignoreReasons.add(getString(R.string.reason_suspended));
		}
		if (!app.getEnabled()) {
			ignoreReasons.add(getString(R.string.reason_app));
		}
		if (stringIgnored) {
			ignoreReasons.add(getString(R.string.reason_string));
		}
		if (event.getText().isEmpty()) {
			ignoreReasons.add(getString(R.string.reason_empty_msg));
		}
		int ignoreRepeat;
		try {
			ignoreRepeat = Integer.parseInt(Common.getPrefs(this).getString(getString(R.string.key_ignore_repeat), null));
		} catch (NumberFormatException e) {
			ignoreRepeat = -1;
		}
		if (lastMsg.contentEquals(newMsg) && (ignoreRepeat == -1 || newMsgTime - lastMsgTime < ignoreRepeat * 1000)) {
			ignoreReasons.add(MessageFormat.format(getString(R.string.reason_identical), ignoreRepeat));
		}
		if (ignoreReasons.isEmpty()) {
			int delay = 0;
			try {
				delay = Integer.parseInt(Common.getPrefs(this).getString(getString(R.string.key_ttsDelay), null));
			} catch (NumberFormatException e) {}
			if (!isScreenOn()) {
				int interval;
				try {
					interval = Integer.parseInt(Common.getPrefs(this).getString(getString(R.string.key_tts_repeat), "0"));
				} catch (NumberFormatException e) {
					interval = 0;
				}
				if (interval > 0) {
					repeatList.add(newMsg);
					if (repeater == null) {
						repeater = new RepeatTimer(interval);
					} else repeater.checkInterval(interval);
				}
			}
			if (delay > 0) {
				final String msg = newMsg;
				new Timer().schedule(new TimerTask() {
					@Override
					public void run() {
						speak(msg, true);
					}
				}, delay * 1000);
			} else speak(newMsg, true);
		} else {
			String reasons = ignoreReasons.toString().replaceAll("\\[|\\]", "");
			Log.i(TAG, "Notification from " + app.getLabel() + " ignored for reason(s): " + reasons);
			NotifyList.setLastIgnore(reasons, true);
			ignoreReasons.clear();
		}
		lastMsg = newMsg;
		lastMsgTime = newMsgTime;
	}
	
	/**
	 * Sends msg to TTS if ignore condition is not met.
	 * @param msg The string to be spoken.
	 * @param isNew True if notification is new, otherwise false if it is being repeated.
	 */
	private void speak(final String msg, boolean isNew) {
		if (ignore(isNew)) return;
		shake.enable();
		ttsParams.clear();
		if (Common.getPrefs(this).getString(getString(R.string.key_ttsStream), null).contentEquals("notification")) {
			ttsParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_NOTIFICATION));
		}
		lastQueueTime = Long.toString(System.currentTimeMillis());
		ttsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, lastQueueTime);
		if (mTts == null) {<commit_msg>Fix crash when notification received with really high TTS Delay.<commit_after>/*
 * Copyright 2012 <NAME>
 *
 * 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.pilot51.voicenotify;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.IllegalFormatException;
import java.util.Timer;
import java.util.TimerTask;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Notification;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.PowerManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;

public class Service extends AccessibilityService {
	private static String TAG = Service.class.getSimpleName();
	private String lastMsg = "";
	private long lastMsgTime;
	private TextToSpeech mTts;
	private AudioManager audioMan;
	private TelephonyManager telephony;
	private final DeviceStateReceiver stateReceiver = new DeviceStateReceiver();
	private RepeatTimer repeater;
	private Shake shake;
	private static boolean isInitialized, isSuspended, isScreenOn, isHeadsetPlugged, isBluetoothConnected;
	private final HashMap<String, String> ttsParams = new HashMap<String, String>();
	private final ArrayList<String> ignoreReasons = new ArrayList<String>(),
	                                repeatList = new ArrayList<String>();
	private String lastQueueTime;
	private OnStatusChangeListener statusListener = new OnStatusChangeListener() {
		@Override
		public void onStatusChanged() {
			if (isSuspended && mTts != null) mTts.stop();
			sendBroadcast(new Intent(getApplicationContext(), WidgetProvider.class)
			              .setAction(WidgetProvider.ACTION_UPDATE));
		}
	};
	
	@Override
	public void onAccessibilityEvent(AccessibilityEvent event) {
		if (!Common.getPrefs(this).getBoolean(getString(R.string.key_toasts), false) && !(event.getParcelableData() instanceof Notification)) {
			return;
		}
		long newMsgTime = System.currentTimeMillis();
		StringBuilder notifyMsg = new StringBuilder();
		if (!event.getText().isEmpty()) {
			for (CharSequence subText : event.getText()) {
				notifyMsg.append(subText);
			}
		}
		final String ttsStringPref = Common.getPrefs(this).getString(getString(R.string.key_ttsString), null);
		App app = AppList.findOrAddApp(event.getPackageName().toString(), this);
		NotifyList.addNotification(app, notifyMsg.toString());
		String newMsg;
		try {
			newMsg = String.format(ttsStringPref.replace("%t", "%1$s").replace("%m", "%2$s"), app.getLabel(), notifyMsg.toString().replaceAll("[\\|\\[\\]\\{\\}\\*<>]+", " "));
		} catch(IllegalFormatException e) {
			Log.w(TAG, "Error formatting custom TTS string!");
			e.printStackTrace();
			newMsg = ttsStringPref;
		}
		final String[] ignoreStrings = Common.getPrefs(this).getString(getString(R.string.key_ignore_strings), "").toLowerCase().split("\n");
		boolean stringIgnored = false;
		if (ignoreStrings != null) {
			for (String s : ignoreStrings) {
				if (s.length() != 0 && notifyMsg.toString().toLowerCase().contains(s)) {
					stringIgnored = true;
					break;
				}
			}
		}
		if (isSuspended) {
			ignoreReasons.add(getString(R.string.reason_suspended));
		}
		if (!app.getEnabled()) {
			ignoreReasons.add(getString(R.string.reason_app));
		}
		if (stringIgnored) {
			ignoreReasons.add(getString(R.string.reason_string));
		}
		if (event.getText().isEmpty()) {
			ignoreReasons.add(getString(R.string.reason_empty_msg));
		}
		int ignoreRepeat;
		try {
			ignoreRepeat = Integer.parseInt(Common.getPrefs(this).getString(getString(R.string.key_ignore_repeat), null));
		} catch (NumberFormatException e) {
			ignoreRepeat = -1;
		}
		if (lastMsg.contentEquals(newMsg) && (ignoreRepeat == -1 || newMsgTime - lastMsgTime < ignoreRepeat * 1000)) {
			ignoreReasons.add(MessageFormat.format(getString(R.string.reason_identical), ignoreRepeat));
		}
		if (ignoreReasons.isEmpty()) {
			int delay = 0;
			try {
				delay = Integer.parseInt(Common.getPrefs(this).getString(getString(R.string.key_ttsDelay), null));
			} catch (NumberFormatException e) {}
			if (!isScreenOn()) {
				int interval;
				try {
					interval = Integer.parseInt(Common.getPrefs(this).getString(getString(R.string.key_tts_repeat), "0"));
				} catch (NumberFormatException e) {
					interval = 0;
				}
				if (interval > 0) {
					repeatList.add(newMsg);
					if (repeater == null) {
						repeater = new RepeatTimer(interval);
					} else repeater.checkInterval(interval);
				}
			}
			if (delay > 0) {
				final String msg = newMsg;
				new Timer().schedule(new TimerTask() {
					@Override
					public void run() {
						speak(msg, true);
					}
				}, delay * 1000L);
			} else speak(newMsg, true);
		} else {
			String reasons = ignoreReasons.toString().replaceAll("\\[|\\]", "");
			Log.i(TAG, "Notification from " + app.getLabel() + " ignored for reason(s): " + reasons);
			NotifyList.setLastIgnore(reasons, true);
			ignoreReasons.clear();
		}
		lastMsg = newMsg;
		lastMsgTime = newMsgTime;
	}
	
	/**
	 * Sends msg to TTS if ignore condition is not met.
	 * @param msg The string to be spoken.
	 * @param isNew True if notification is new, otherwise false if it is being repeated.
	 */
	private void speak(final String msg, boolean isNew) {
		if (ignore(isNew)) return;
		shake.enable();
		ttsParams.clear();
		if (Common.getPrefs(this).getString(getString(R.string.key_ttsStream), null).contentEquals("notification")) {
			ttsParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_NOTIFICATION));
		}
		lastQueueTime = Long.toString(System.currentTimeMillis());
		ttsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, lastQueueTime);
		if (mTts == null) {
{'fraction_non_alphanumeric': 0.12394641782059, 'fraction_numerical': 0.004214328717639976, 'mean_word_length': 4.530170620058261, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '119022', 'n_tokens_mistral': 4427, 'n_tokens_neox': 3964, 'n_words': 923}